Sviluppare un Expert Advisor per il trading da zero (Parte 11): Sistema di ordini incrociati

Daniel Jose | 11 novembre, 2022

Introduzione

C'è un tipo di asset che rende la vita dei trader molto difficile — si tratta dei contratti future. Ma perché rendono la vita difficile? Quando il contratto di uno strumento finanziario scade, viene creato un nuovo contratto che poi possiamo negoziare. In realtà, alla scadenza del contratto, dobbiamo terminare qualsiasi sua analisi, salvare tutto come modello e importare questo modello in un nuovo contratto per continuare questa analisi. Si tratta di una situazione comune a tutti coloro che negoziano questo tipo di asset, ma anche i contratti future hanno una certa storia e grazie a questa storia possiamo analizzarli su base continuativa.

I trader professionisti amano analizzare alcune informazioni passate, nel qual caso è necessario un secondo grafico. Ora, non è necessario avere il secondo grafico se si utilizzano gli strumenti appropriati. Uno di questi strumenti è l'utilizzo del sistema di ordini incrociati.


Pianificazione

Nel primo articolo di questa serie, abbiamo già parlato di questo tipo di ordini, ma non siamo arrivati all'implementazione. In quell'articolo ci siamo concentrati su altre cose, poiché stavamo lanciando un sistema completo in grado di funzionare con la piattaforma MetaTrader 5. In questo articolo mostreremo come implementare questa funzionalità.

Per capire le ragioni della creazione di questa funzionalità, date un’occhiata alle due immagini seguenti:

           

L'immagine a sinistra rappresenta un tipico contratto future, in questo caso il MINI DOLLAR FUTURE, che è iniziato pochi giorni fa, come si può vedere dal grafico. Il grafico a destra mostra lo stesso contratto e contiene dati aggiuntivi che rappresentano effettivamente i valori dei contratti scaduti, quindi il grafico a destra è un grafico storico. Il grafico a destra è più adatto per analizzare i vecchi livelli di supporto e resistenza. Ma sorge un problema se abbiamo bisogno di tradarlo. Ed è mostrato di seguito:

          

Come si può vedere, il simbolo negoziato è specificato in CHART TRADE, e anche se utilizziamo la cronologia CHART TRADE dice che possiamo inviare un ordine - questo può essere visto dalla barra degli strumenti. Nell'immagine di sinistra, il grafico ha un ordine impostato per il contratto corrente, ma nell'immagine di destra l'ordine è visibile solo nella casella dei messaggi, mentre non c'è nulla di visibile sul grafico.

Si potrebbe pensare che si tratti solo di un problema di visualizzazione, invece no, tutto è molto più complicato. Questo è ciò che andremo a trattare in questo articolo.

Importante! Qui vedremo come creare regole per poter utilizzare i dati storici per il lavoro. Nel nostro caso, queste regole si concentreranno sul lavoro con il Mini Dollaro (WDO) e il Mini Indice (WIN), che sono negoziati sulla Borsa brasiliana (B3). Una corretta comprensione vi permetterà di adattare le regole a qualsiasi tipo di contratto future, di qualsiasi borsa del mondo.

Il sistema non è limitato a un asset o a un altro, mentre si tratta di adattare le parti giuste del codice. Se questo viene fatto correttamente, allora avremo un Expert Advisor con il quale non dovremo preoccuparci di sapere se il contratto di un asset si sta avvicinando alla scadenza e quale sarà il contratto successivo - l'EA lo farà per noi, sostituendo il contratto con quello corretto secondo le necessità.


Come comprendere le regole del gioco

I contratti future WDO (mini dollaro), WIN (mini indice), DOL (dollar future) e IND (index future) seguono regole molto precise per quanto riguarda la scadenza e le specifiche del contratto. Innanzitutto, vediamo come scoprire la data di scadenza del contratto:


Prestate attenzione alle informazioni evidenziate: il blu indica la data di scadenza del contratto e il rosso la data in cui il contratto cesserà di esistere, dopo di che non sarà più negoziato. Saperlo è molto importante.

La durata del contratto è specificata nel contratto stesso, ma non vi è specificato alcun nome. Fortunatamente, possiamo trovare facilmente il nome in base alle regole che sono rigorose e vengono utilizzate in tutto il mercato. Nel caso dei contratti future sul dollaro e sugli indici, si ha quanto segue:

Le prime tre lettere indicano il tipo di contratto:

Codice Contratto
WIN Contratto future sull'indice Mini Ibovespa 
IND Contratto future sull'indice Ibovespa
WDO Contratto future sul mini dollaro
DOL Contratto future sul dollaro

Questo codice è seguito da una lettera che indica il mese di scadenza del contratto:

Mese di scadenza Lettera che rappresenta WDO e DOL Lettera che rappresenta WIN e IND 
Gennaio F
 
Febbraio  G  G
Marzo  H  
Aprile  J  J
Maggio  K  
Giugno  M  M
Luglio  N  
Agosto  Q  Q
Settembre,  U  
Ottobre  V  V
Novembre  X  
Dicembre  Z  Z

Questi sono seguiti da due cifre che rappresentano l'anno di scadenza del contratto. Ad esempio, un contratto future sul dollaro con scadenza aprile 2022 è indicato come DOLJ22. Questo è il contratto che può essere tradato fino all'inizio di maggio. Con l'inizio del mese di maggio, il contratto scadrà. Poiché la regola è leggermente diversa per WIN e IND, il contratto scade effettivamente il mercoledì più vicino al 15 del mese indicato. Quindi, la regola è più complicata, ma l'EA è in grado di gestirla e fornirà sempre il contratto corretto.


Implementazione

Il nostro EA ha già i punti necessari per ricevere le regole. Qui dovremo implementare alcune impostazioni relative al sistema di invio degli ordini. Benissimo, mettiamoci al lavoro! Prima di tutto, aggiungere il seguente codice all'oggetto della classe C_Terminal:

void CurrentSymbol(void)
{
        MqlDateTime mdt1;
        string sz0, sz1;
        datetime dt = TimeLocal();
            
        sz0 = StringSubstr(m_Infos.szSymbol = _Symbol, 0, 3);                           
        if ((sz0 != "WDO") && (sz0 != "DOL") && (sz0 != "WIN") && (sz0 != "IND")) return;
        sz1 = ((sz0 == "WDO") || (sz0 == "DOL") ? "FGHJKMNQUVXZ" : "GJMQVZ");
        TimeToStruct(TimeLocal(), mdt1);
        for (int i0 = 0, i1 = mdt1.year - 2000;;)
        {
                m_Infos.szSymbol = StringFormat("%s%s%d", sz0, StringSubstr(sz1, i0, 1), i1);
                if (i0 < StringLen(sz1)) i0++; else
                {
                        i0 = 0;
                        i1++;
                }
                if (macroGetDate(dt) < macroGetDate(SymbolInfoInteger(m_Infos.szSymbol, SYMBOL_EXPIRATION_TIME))) break;
        }
}

Questo codice utilizza le regole viste in precedenza per generare il nome dell’asset. Per assicurarci di utilizzare sempre il contratto corrente, implementeremo un controllo mostrato nella riga evidenziata, ovvero che l'asset sia valido per la piattaforma e che l'EA userà il nome generato. Se si desidera lavorare con altri contratti future, è necessario adattare il codice precedente in modo che il nome venga generato correttamente, poiché il nome può variare da caso a caso. Ma il codice non è limitato solo alle attività ad esso collegate - può essere utilizzato per riflettere qualsiasi tipo di contratto future, purché si utilizzi una regola corretta.

Segue la parte con i dettagli dell'ordine. Se utilizzate il sistema in questa fase di sviluppo, noterete il seguente comportamento:


In altre parole, è già possibile avere la modalità di ordine incrociato, ma non è ancora pienamente implementata - non c'è alcuna indicazione dell'ordine sul grafico. Non è così difficile da implementare come molti di voi potrebbero immaginare, poiché abbiamo bisogno di indicare gli ordini utilizzando delle linee orizzontali. Ma non è tutto. Quando utilizziamo gli ordini incrociati, ci sfuggono alcuni elementi forniti da MetaTrader 5 e quindi dobbiamo implementare la logica mancante in modo che il sistema degli ordini possa funzionare in modo sicuro, stabile e affidabile. In caso contrario, l'uso di ordini incrociati può causare problemi.

Da questo punto di vista, non sembra così semplice. In realtà, non è semplice, poiché dovremo creare tutta la logica che la piattaforma MetaTrader offre in origine. Quindi, la prima cosa da fare è dimenticare il sistema interno della MetaTrader - non sarà più disponibile dal momento in cui inizieremo a utilizzare il sistema di ordini incrociati.

D'ora in poi sarà il ticket dell'ordine a dettare le regole. Ma questo comporta alcune conseguenze negative. Uno dei più negativi è che non sappiamo quanti ordini vengono inseriti su un grafico. Limitare il loro numero sarebbe sicuramente spiacevole per il trader. Pertanto, dobbiamo fare qualcosa che consenta al trader di utilizzare il sistema nello stesso modo in cui viene normalmente fatto con la logica completa di MetaTrader. Questo è il primo problema da risolvere.


Classe C_HLineTrade

Per risolvere questo problema, creeremo una nuova classe C_HLineTrade, che sostituirà il sistema di visualizzazione degli ordini sul grafico, fornito da MetaTrader 5. Iniziamo con la dichiarazione della classe:

class C_HLineTrade
{
#define def_NameHLineTrade "*HLTSMD*"
        protected:
                enum eHLineTrade {HL_PRICE, HL_STOP, HL_TAKE};
        private :
                color   m_corPrice,
                        m_corStop,
                        m_corTake;
                string  m_SelectObj;

Notare che qui sono state definite alcune cose - verranno utilizzate frequentemente nel codice. Pertanto, vi preghiamo di prestare molta attenzione alle successive modifiche, che saranno davvero molte. Quindi dichiariamo il costruttore e il distruttore della classe:
C_HLineTrade() : m_SelectObj("")
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        RemoveAllsLines();
};
//+------------------------------------------------------------------+  
~C_HLineTrade()
{
        RemoveAllsLines();
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, true);
};

Il costruttore impedisce che le linee originali siano visibili, mentre il distruttore le riporta sul grafico. Entrambe le funzioni hanno una funzione comune, che è la seguente:

void RemoveAllsLines(void)
{
        string sz0;
        int i0 = StringLen(def_NameHLineTrade);
                                
        for (int c0 = ObjectsTotal(Terminal.Get_ID(), -1, -1); c0 >= 0; c0--)
        {
                sz0 = ObjectName(Terminal.Get_ID(), c0, -1, -1);
                if (StringSubstr(sz0, 0, i0) == def_NameHLineTrade) ObjectDelete(Terminal.Get_ID(), sz0);
        }
}

La linea evidenziata controlla se l'oggetto (in questo caso è una linea orizzontale), è uno degli oggetti utilizzati dalla classe. Se lo è, l'oggetto verrà cancellato. Notare che non sappiamo quanti oggetti abbiamo, ma il sistema controllerà oggetto per oggetto, cercando di ripulire tutto ciò che è stato creato dalla classe. La funzione successiva consigliata di questa classe è mostrata di seguito:

inline void SetLineOrder(ulong ticket, double price, eHLineTrade hl, bool select)
{
        string sz0 = def_NameHLineTrade + (string)hl + (string)ticket, sz1;
                                
        if (price <= 0)
        {
                ObjectDelete(Terminal.Get_ID(), sz0);
                return;
        }
        if (!ObjectGetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, 0, sz1))
        {
                ObjectCreate(Terminal.Get_ID(), sz0, OBJ_HLINE, 0, 0, 0);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_COLOR, (hl == HL_PRICE ? m_corPrice : (hl == HL_STOP ? m_corStop : m_corTake)));
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_WIDTH, 1);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_STYLE, STYLE_DASHDOT);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTABLE, select);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), sz0, OBJPROP_BACK, true);
                ObjectSetString(Terminal.Get_ID(), sz0, OBJPROP_TOOLTIP, (string)ticket + " "+StringSubstr(EnumToString(hl), 3, 10));
        }
        ObjectSetDouble(Terminal.Get_ID(), sz0, OBJPROP_PRICE, price);
}
Per questa funzione, non importa quanti oggetti verranno creati e se l'oggetto esiste nel momento in cui viene chiamata. Assicura che la linea venga creata e posizionata nel punto giusto. Questa linea creata sostituisce quella originariamente utilizzata in MetaTrader.

Il nostro scopo è quello di renderlo funzionale piuttosto che bello e piacevole. Per questo motivo le linee non vengono selezionate quando vengono create - se necessario, è possibile modificare questa modalità. Ma io uso il sistema di messaggistica di MetaTrader 5 per posizionare le linee. Quindi, per poterli spostare, è necessario indicarlo esplicitamente. Per indicare quale linea sia da regolare, abbiamo un'altra funzione:

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (m_SelectObj != "") ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        m_SelectObj = sparam;
                };
        }
}
Questa funzione implementa la selezione di una linea. Se è selezionata un'altra linea, la selezione precedente viene annullata. Tutto questo è semplice. La funzione manipola solo le linee effettivamente gestite dalla classe. Un'altra funzione di questa classe, che vale la pena menzionare, è la seguente:
bool GetNewInfosOrder(const string &sparam, ulong &ticket, double &price, eHLineTrade &hl)
{
        int i0 = StringLen(def_NameHLineTrade);
                                
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                hl = (eHLineTrade) StringToInteger(StringSubstr(sparam, i0, 1));
                ticket = (ulong)StringToInteger(StringSubstr(sparam, i0 + 1, StringLen(sparam)));
                price = ObjectGetDouble(Terminal.Get_ID(), sparam, OBJPROP_PRICE);
                return true;
        }
        return false;
}
Questa funzione è forse la più importante di questa classe: poiché non sappiamo quante linee ci sono sul grafico, dobbiamo sapere quale linea l'utente sta manipolando. Questa funzione ha proprio questa funzione - indica al sistema quale linea viene manipolata.

Ma questa è solo una piccola parte di ciò che dobbiamo fare. Il sistema è ancora lontano dall'essere funzionante. Passiamo quindi alla fase successiva - aggiungeremo e modificheremo le funzioni della classe C_Router, responsabile dell'instradamento degli ordini. Questa classe eredita le funzionalità appena create nella classe C_HLineTrade. Vedi il codice seguente:

#include "C_HLineTrade.mqh"
//+------------------------------------------------------------------+
class C_Router : public C_HLineTrade



Nuova classe C_Router

La classe C_Router di partenza aveva una limitazione che consentiva di avere un solo ordine aperto. Questa limitazione sarà eliminata, per cui è necessario apportare importanti modifiche alla classe C_Router.

La prima modifica riguarda la funzione di aggiornamento della classe, che ora si presenta in questo modo:

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal() - 1;
        o = OrdersTotal() - 1;
        if ((memPositions != p) || (memOrder != o))
        {
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
                RemoveAllsLines();
                ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
                memOrder = o;
                memPositions = p;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, PositionGetDouble(POSITION_SL), HL_STOP, true);
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                SetLineOrder(ul, OrderGetDouble(ORDER_PRICE_OPEN), HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};

In precedenza, questa funzione raccoglieva dati su una sola posizione aperta e li salvava nel suo osservatorio. Ora la funzione visualizzerà assolutamente tutte le posizioni aperte e gli ordini pendenti sul grafico. È sicuramente un sostituto del sistema fornito da MetaTrader. Poiché si tratta di cose serie, è importante capire come funziona, perché se fallisse, ciò si ripercuoterebbe sull'intero sistema di ordini incrociati. Quindi, prima di operare su un conto reale, testiamo questo sistema su un conto demo. Tali sistemi devono essere testati adeguatamente fino a quando non siamo assolutamente sicuri che tutto funzioni come dovrebbe. Per prima cosa, è necessario configurare il sistema, perché funziona in modo leggermente diverso rispetto a MetaTrader 5.

Guardate le righe evidenziate e rispondete onestamente: È chiaro cosa fanno realmente? Il motivo del perché di queste due linee di codice sarà chiaro in seguito, quando parleremo della classe C_OrderView più avanti in questo articolo. Senza queste due righe, il codice è molto instabile e funziona in modo strano. Il resto del codice è abbastanza semplice - crea ogni linea tramite l'oggetto della classe C_HLineTrade. In questo caso abbiamo una sola riga che non può essere selezionata. Questo è facilmente indicato, come mostrato nel codice sottostante:

SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);

In altre parole, il sistema è diventato molto semplice e lineare. La funzione viene richiamata dall'EA durante l’evento OnTrade:

C_TemplateChart Chart;

// ... Expert Advisor code ...

void OnTrade()
{
        Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, Chart.UpdateRoof(), C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]);
        Chart.UpdatePosition();
}

// ... The rest of the Expert Advisor code ...

Il codice evidenziato abilita l'aggiornamento dei comandi sullo schermo. Notate che stiamo usando il grafico C_TemplateChart per questo - perché la struttura delle classi nel sistema è cambiata. La nuova struttura è illustrata di seguito:

Questa struttura consente un flusso diretto di messaggi direttamente all'interno dell'EA . In caso di dubbi sul modo in cui il flusso di messaggi arriva a una determinata classe, si può dare un'occhiata a questo grafico sull'ereditarietà delle classi. L'unica classe considerata pubblica è la classe oggetto C_Terminal, mentre tutte le altre sono gestite tramite ereditarietà tra le classi e nessuna delle variabili sono pubbliche in questo sistema.

Ora, poiché il sistema non analizza solo un singolo ordine, è necessario comprendere qualcos'altro: Come capire il risultato delle operazioni? Perché è importante? Quando si ha una sola posizione aperta, il sistema è in grado di capire tutto, ma quando il numero di posizioni aperte aumenta, è necessario capire cosa sta succedendo. Ecco la funzione che fornisce queste informazioni:

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

Non ci sono molti cambiamenti. Osservate il codice della funzione evidenziato:

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

La funzione è composta da tre parti evidenziate: la parte gialla informa sul risultato delle posizioni aperte, mentre le parti verdi controllano la posizione nel caso in cui lo stop loss non venga rispettato a causa dell'elevata volatilità, nel qual caso è necessario chiudere la posizione il prima possibile. Pertanto, questa funzione non restituisce il risultato di una singola posizione, tranne nel caso di una posizione aperta per un asset specifico.

Esistono anche altre funzioni che aiutano il sistema a continuare a funzionare quando utilizziamo il modello di ordine incrociato. Guardateli nel codice qui sotto:

bool ModifyOrderPendent(const ulong Ticket, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        if (Ticket == 0) return false;
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_MODIFY;
        TradeRequest.order      = Ticket;
        TradeRequest.price      = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.type_time  = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.expiration = 0;
        return OrderSend(TradeRequest, TradeResult);
};
//+------------------------------------------------------------------+
bool ModifyPosition(const ulong Ticket, const double Take, const double Stop)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        if (!PositionSelectByTicket(Ticket)) return false;
        TradeRequest.action     = TRADE_ACTION_SLTP;
        TradeRequest.position   = Ticket;
        TradeRequest.symbol     = PositionGetString(POSITION_SYMBOL);
        TradeRequest.tp         = NormalizeDouble(Take, Terminal.GetDigits());
        TradeRequest.sl         = NormalizeDouble(Stop, Terminal.GetDigits());
        return OrderSend(TradeRequest, TradeResult);
};
Il primo è responsabile della modifica dell'ordine ancora aperto, mentre l'altro modifica la posizione aperta. Anche se sembrano uguali, non lo sono. Esiste un'altra funzione importante per il sistema:
bool RemoveOrderPendent(ulong Ticket)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action     = TRADE_ACTION_REMOVE;
        TradeRequest.order      = Ticket;       
        return OrderSend(TradeRequest, TradeResult);
};

Con quest'ultima funzione completiamo la considerazione della classe C_Router. Abbiamo implementato il sistema di base che copre le funzionalità normalmente supportate in MetaTrader - no, a causa del sistema di ordini incrociati, non possiamo più contare su questo supporto. Tuttavia, il sistema non è ancora completo. Dobbiamo aggiungere qualcos'altro per rendere il sistema davvero funzionante. Al momento, se c'è un ordine, apparirà come segue. Questo è necessario per poter completare la fase successiva.



Osservate attentamente l'immagine qui sopra. Il riquadro dei messaggi mostra l'ordine aperto e l'asset per la quale è stato aperto. L'asset negoziato è indicato in CHART TRADE. Notare che si tratta della stessa risorsa indicata nella casella del messaggio. Controlliamo ora l'asset visualizzato sul grafico. Il nome può essere controllato nell'intestazione della finestra del grafico. Ma è completamente differente - non si tratta di un asset sul grafico, ma dello storico del mini-indice, il che significa che ora non utilizziamo il sistema interno di MetaTrader 5 ma il sistema di ordini incrociati descritto in questo articolo. Ora abbiamo solo la funzionalità che consente di mostrare dove si trova l'ordine. Ma non è sufficiente, perché vogliamo avere un sistema pienamente funzionale che permetta di operare attraverso il sistema degli ordini incrociati. Quindi, abbiamo bisogno di qualcos'altro. Per quanto riguarda l'evento relativo allo spostamento dell'ordine, questo sarà implementato in un'altra classe.


Nuova funzionalità nella classe C_OrderView

La classe oggetto C_OrderView può fare alcune cose, ma non può ancora gestire i dati degli ordini aperti o pendenti. Tuttavia, quando aggiungiamo un sistema di messaggistica, abbiamo più possibilità di utilizzarlo. Questa è l'unica aggiunta che faremo alla classe per ora. Il codice completo della funzione è mostrato di seguito:

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong           ticket;
        double          price, pp, pt, ps;
        eHLineTrade     hl;
        
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        MoveTo((int)lparam, (int)dparam, (uint)sparam);
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                if (OrderSelect(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        RemoveOrderPendent(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }else if (PositionSelectByTicket(ticket))
                                {
                                        switch (hl)
                                        {
                                                case HL_PRICE:
                                                        ClosePosition(ticket);
                                                        break;
                                                case HL_STOP:
                                                        ModifyPosition(ticket, OrderGetDouble(ORDER_TP), 0);
                                                        break;
                                                case HL_TAKE:
                                                        ModifyPosition(ticket, 0, OrderGetDouble(ORDER_SL));
                                                        break;
                                        }
                                }
                        }
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        C_HLineTrade::Select(sparam);
                        break;
                case CHARTEVENT_OBJECT_DRAG:
                        if (GetNewInfosOrder(sparam, ticket, price, hl))
                        {
                                price = AdjustPrice(price);
                                if (OrderSelect(ticket)) switch(hl)
                                {
                                        case HL_PRICE:
                                                pp = price - OrderGetDouble(ORDER_PRICE_OPEN);
                                                pt = OrderGetDouble(ORDER_TP);
                                                ps = OrderGetDouble(ORDER_SL);
                                                if (!ModifyOrderPendent(ticket, price, (pt > 0 ? pt + pp : 0), (ps > 0 ? ps + pp : 0))) UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), OrderGetDouble(ORDER_TP), price)) UpdatePosition();
                                                break;
                                        case HL_TAKE:
                                                if (!ModifyOrderPendent(ticket, OrderGetDouble(ORDER_PRICE_OPEN), price, OrderGetDouble(ORDER_SL))) UpdatePosition();
                                                break;
                                }
                                if (PositionSelectByTicket(ticket)) switch (hl)
                                {
                                        case HL_PRICE:
                                                UpdatePosition();
                                                break;
                                        case HL_STOP:
                                                ModifyPosition(ticket, PositionGetDouble(POSITION_TP), price);
                                                break;
                                        case HL_TAKE:
                                                ModifyPosition(ticket, price, PositionGetDouble(POSITION_SL));
                                                break;
                                }
                        };
                break;
        }
}

Questo codice completa il sistema di ordini incrociati. Le nostre capacità sono aumentate in modo da poter fare quasi le stesse cose che erano possibili senza il sistema di ordini incrociati. In generale, la funzione dovrebbe essere abbastanza chiara. Ma ha un tipo di evento non molto comune: CHARTEVENT_OBJECT_DELETE. Quando l'utente cancella una linea, ciò si riflette sul grafico e sul sistema degli ordini, pertanto è necessario prestare molta attenzione quando si inizia a cancellare le linee dal grafico. Non dobbiamo preoccuparci quando rimuoviamo l'EA dal grafico, poiché gli ordini rimarranno intatti, come mostrato nella seguente animazione:


Ma se l'EA si trova sul grafico, bisogna fare molta attenzione quando si eliminano le linee, soprattutto quelle nascoste nella lista degli oggetti. Altrimenti, si può vedere di seguito cosa succede nel sistema di ordini quando si rimuovono le righe create dal sistema di ordini incrociati.

Per concludere la demo del sistema, vediamo cosa succede all'ordine quando trasciniamo le linee del prezzo. Ricordate quanto segue: la linea trascinata deve essere selezionata; se non è selezionata, sarà impossibile muoverla. La variazione di prezzo avverrà quando la linea viene rilasciata sul grafico, mentre prima il prezzo rimarrà nella stessa posizione.


Se è difficile capire se una linea è stata selezionata o meno, si possono apportare delle modifiche al codice di selezione. Le modifiche sono evidenziate di seguito. questa modifica è già stata implementata nella versione allegata.

inline void Select(const string &sparam)
{
        int i0 = StringLen(def_NameHLineTrade);
                
        if (m_SelectObj != "")
        {
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_SELECTED, false);
                ObjectSetInteger(Terminal.Get_ID(), m_SelectObj, OBJPROP_WIDTH, 1);
        }
        m_SelectObj = "";
        if (StringSubstr(sparam, 0, i0) == def_NameHLineTrade)
        {
                if (ObjectGetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTABLE))
                {
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_SELECTED, true);
                        ObjectSetInteger(Terminal.Get_ID(), sparam, OBJPROP_WIDTH, 2);
                        m_SelectObj = sparam;
                };
        }
}

Il risultato di questa modifica del codice è visibile nella figura seguente.


Conclusioni

Ho quindi mostrato come creare un sistema di ordini incrociati in MetaTrader. Spero che questo sistema sia utile a chiunque voglia utilizzare queste conoscenze. Ma ricordate quanto segue: prima di iniziare a fare trading su un conto live con questo sistema, dovreste testarlo il più accuratamente possibile in differenti scenari di mercato, perché sebbene questo sistema sia implementato nella piattaforma MetaTrader, non ha quasi alcun supporto da parte della piattaforma in termini di gestione degli errori, quindi se si verificano per caso, dovrete agire rapidamente per non subire grosse perdite. Ma testandolo in diversi scenari, è possibile scoprire dove sorgono i problemi: nei movimenti, nel numero massimo di ordini che il computer può gestire, nello spread massimo consentito per il sistema di analisi, nel livello di volatilità consentito per gli ordini aperti, poiché maggiore è il numero di ordini aperti e di informazioni da analizzare, più è probabile che accada qualcosa di negativo. Questo perché ogni ordine viene analizzato su ogni tick ricevuto dal sistema e questo può essere un problema quando sono aperti molti ordini contemporaneamente.

Vi consiglio di non fidarvi di questo sistema finché non lo avrete testato su un conto demo in differenti scenari. Anche se il codice sembra perfetto, non presenta alcuna analisi degli errori.

Vi allego il codice di tutti gli Expert Advisor come sono attualmente.