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

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

MetaTrader 5Trading | 16 giugno 2023, 10:13
657 0
Daniel Jose
Daniel Jose

Introduzione

Da quando abbiamo iniziato a documentare questo EA dal primissimo articolo in questa serie Sviluppare un Expert Advisor per il trading da zero, ha subito varie modifiche e miglioramenti mantenendo lo stesso modello di sistema di ordini sul grafico. È molto semplice e funzionale. Tuttavia, in molte situazioni, non è adatto al trading reale.

Naturalmente, potremmo aggiungere alcune cose al sistema originale in modo da avere delle informazioni sugli ordini, sia aperti che pendenti. Ma questo trasformerebbe il nostro codice in un Frankenstein che alla fine renderebbe il processo di miglioramento un vero incubo. Anche se hai la tua metodologia, andrà persa col tempo, man mano che il codice diventa grande e troppo complicato.

Quindi, dobbiamo creare un sistema completamente diverso in termini di modello di ordine utilizzato. Allo stesso tempo, dovrebbe essere facile per il trader capire, fornendo tutte le informazioni di cui abbiamo bisogno per lavorare in sicurezza.


Nota importante

Il sistema che sto descrivendo in questo articolo è progettato per conti netting ACCOUNT_MARGIN_MODE_RETAIL_NETTING, che consente di avere una sola posizione aperta per simbolo. Se stai utilizzando un conto hedging ACCOUNT_MARGIN_MODE_RETAIL_HEDGING, l'articolo non fornirà nulla al tuo EA poiché in questo caso puoi avere tutte le posizioni di cui hai bisogno e non interferiranno l'una con l'altra. Quindi, puoi semplicemente resettare ed eliminare tutte le modifiche dal codice finale.

La modalità ACCOUNT_MARGIN_MODE_RETAIL_HEDGING viene spesso utilizzata quando desideri che un EA funzioni automaticamente, aprendo e chiudendo posizioni senza la tua partecipazione e allo stesso tempo continui a fare trading manualmente. Inoltre, tradi lo stesso asset che negozia il tuo EA. Con l’hedging, le attività del tuo EA non influiranno su nessuna delle tue operazioni e quindi avrai posizioni indipendenti.

Per questo motivo, evidenzierò tutte le parti di codice aggiunte o modificate. Implementeremo tutte le modifiche e le aggiunte lentamente e gradualmente, così se hai bisogno di rimuovere qualcosa dal tuo codice, sarai in grado di trovare facilmente le parti di codice pertinenti.

Sebbene le modifiche possano essere rimosse, c'è una parte in cui controllo il sistema. Anche se salvi le modifiche che considereremo qui, puoi utilizzare questo EA su qualsiasi tipo di conto di trading, NETTING e HEDGING, perché l'Expert Advisor verificherà la tipologia del modello e si adatterà di conseguenza.


1.0. Pianificazione

Infatti, la prima cosa da fare, è capire cosa succede agli ordini che vengono inseriti nel sistema ed evasi non appena vengono raggiunti gli appropriati prezzi. Molti trader forse non lo sanno, o meglio, non ci hanno mai pensato e quindi non hanno effettuato alcun test per poter comprendere questa idea e quindi implementare un sistema adeguato ma affidabile.

Per capire cosa succede effettivamente, analizziamo un semplice esempio: hai una posizione aperta, diciamo Buy, per un certo asset con il volume iniziale di 1 lotto. Poi effettui un nuovo ordine di acquisto di 2 lotti a un prezzo leggermente più alto. Fin qui tutto bene, niente di speciale. Ma non appena questi 2 lotti verranno acquistati, succederà qualcosa, ed è qui che sta il problema.

Una volta acquistati questi due lotti, avrai 3 lotti, ma il sistema aggiornerà il tuo prezzo iniziale e lo imposterà sulla media. Ora questo sembra chiaro, e tutti lo capiscono. Ma cosa succede ai livelli di stop loss e take profit della nuova posizione?

Molti trader non conoscono la risposta, ma è meglio che ci pensino. Se stai utilizzando un sistema di ordini OCO (One Cancel The Other) in tutte le transazioni, puoi notare qualcosa di interessante ogni volta che apri o chiudi una posizione con una parte del volume totale tradato.

Nell'articolo sugli Ordini Incrociati, ho presentato un modo per piazzare take profit e stop loss direttamente sul grafico, senza utilizzare il sistema MetaTrader 5 standard. In effetti, questo metodo è quasi identico al sistema MetaTrader 5, poiché la sua funzionalità è molto vicina a quella della piattaforma, ma con le dovute proporzioni. Tuttavia, dopo aver effettuato alcuni test, ho scoperto che quando abbiamo un ordine OCO aperto e un ordine in sospeso, anche l'ordine OCO viene catturato dal sistema di trading perché il prezzo ha raggiunto il valore specificato nell'ordine. Oltre a un nuovo prezzo medio, abbiamo anche una modifica nei valori di take profit e stop loss — vengono modificati in quelli specificati nell'ultimo ordine OCO acquisito. Quindi, a seconda di come è configurato, l'EA lo chiuderà immediatamente dopo che il sistema di trading riporta nuovi valori di take profit e stop loss.

Ciò accade a causa del seguente controllo implementato nell'EA:

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;
};

Le righe evidenziate eseguono questo controllo e chiudono un ordine nel caso in cui il prezzo esca dal canale formato dai valori di take profit e stop loss.

È importante sapere questo, perché non stiamo parlando di una mancanza dell’EA, di un problema o di un difetto del sistema di trading. Il vero problema è che la maggior parte delle volte non prestiamo la dovuta attenzione a ciò che sta accadendo e ignoriamo la questione fino a quando non è più possibile chiudere un occhio su questo fatto.

Se fai trading in modo che non ci sia la media del prezzo, non vedrai accadere questo genere di cose. Ma c'è una gamma molto ampia di operazioni in cui un prezzo medio è appropriato e persino necessario. Quindi, in questi casi, se stai utilizzando il sistema senza conoscere i dettagli di cui sopra, puoi uscire dalla posizione prima di quanto vorresti, anche se hai precedentemente modificato di conseguenza l'ordine OCO della posizione aperta. Non appena viene catturato un ordine OCO pendente, i valori di soglia ( ogni volta che parlo di soglia mi riferisco ai precedenti take profit e stop loss) verranno sostituiti con quelli specificati nell'ordine OCO pendente recentemente catturato.

C'è un modo per correggerlo o per evitarlo: non usare ordini OCO, almeno quando hai già una posizione aperta. Tutti gli altri ordini immessi nel sistema devono essere di tipo semplice, senza impostare take profit e stop loss.

Fondamentalmente, questo è tutto. Ma quando un EA è sul grafico, è lì per aiutarci, semplificandoci la vita. Quindi, non avrebbe senso programmare un Expert Advisor se non sei in grado di usarlo dopo.


2.0. Implementazione di un nuovo sistema

Dobbiamo apportare piccole modifiche al codice per implementare il sistema e assicurarci che funzioni come ci si aspetta — l'EA dovrebbe aiutarci e assisterci evitando errori.

Non è molto difficile, ma queste modifiche garantiscono che non correremo mai il rischio che l'ordine OCO arrivi in ​​un momento indesiderato, causando una vera confusione nelle nostre operazioni.

Cominciamo con le seguenti modifiche:


2.0.1. Modifica della classe C_Router

La classe C_Router è responsabile dell'analisi e dell'invio degli ordini per noi. Aggiungiamogli una variabile privata. Quando viene trovata una posizione aperta per l'asset tradato dall'EA, questa variabile memorizzerà le relative informazioni. Ogni volta che l’EA vuole sapere se c'è una posizione aperta, ce lo comunica.

Questa implementazione è illustrata nel codice seguente. Tuttavia, questo codice sarà ulteriormente modificato nell'articolo. Voglio solo mostrare tutte le modifiche passo dopo passo, così che tu capisca il modo in cui il processo di modifica effettivamente sembrava.

//+------------------------------------------------------------------+
inline bool ExistPosition(void) const { return m_bContainsPosition; }
//+------------------------------------------------------------------+
void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                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);
        }
};
//+------------------------------------------------------------------+


Le righe evidenziate mostrano le aggiunte richieste per eseguire tutti i controlli che verranno aggiunti più tardi in determinati punti del codice dell’EA.

In un certo senso, potremmo implementare tutti i controlli e gli aggiustamenti solo nella classe C_Router, ma questo non sarà sufficiente. Lo spiegherò più tardi. Per ora, continuiamo con le modifiche. Dopo aver creato il controllo di cui sopra, aggiungiamo un costruttore per inizializzare correttamente la variabile appena aggiunta.

C_Router() : m_bContainsPosition(false) {}


Ora modifichiamo la funzione che piazza gli ordini pendenti come segue:

ulong CreateOrderPendent(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        double last = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_LAST);
                                
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_PENDING;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? (last >= Price ? ORDER_TYPE_BUY_LIMIT : ORDER_TYPE_BUY_STOP) : (last < Price ? ORDER_TYPE_SELL_LIMIT : ORDER_TYPE_SELL_STOP));
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "Order Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                                
        return TradeResult.order;
};


Le parti evidenziate sono le modifiche da implementare.

Ora torniamo al codice aggiornato per apportare una nuova modifica. Ricorda che viene chiamato dalla funzione OnTrade e viene chiamato ogni volta che gli ordini vengono modificati. Questo può essere visto nel codice qui sotto:

void UpdatePosition(void)
{
        static int memPositions = 0, memOrder = 0;
        ulong ul;
        int p, o;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                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())
        {
                if (m_bContainsPosition)
                {
                        ModifyOrderPendent(ul, OrderGetDouble(ORDER_PRICE_OPEN), 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                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);
        }
};

Ora dobbiamo assicurarci che l'utente non converta un semplice ordine pendente in un ordine OCO pendente quando c'è già una posizione aperta, cioè se l'utente apre il pannello degli strumenti e prova a modificare il take profit o lo stop loss. Quando l'utente tenta di farlo, il server di trading ci informerà tramite la funzione OnTrade, quindi l'EA verrà immediatamente a conoscenza della modifica e annullerà la modifica apportata dall'utente, garantendo l'affidabilità del sistema.

Ma c'è un'altra cosa che è necessario modificare; riguarda gli ordini a mercato. Questa è una modifica molto semplice in quanto non richiede alcuna modifica relativa ai controlli. Il codice funzione è mostrato di seguito:

ulong ExecuteOrderInMarket(const bool IsBuy, const double Volume, const double Price, const double Take, const double Stop, const bool DayTrade = true)
{
        ZeroMemory(TradeRequest);
        ZeroMemory(TradeResult);
        TradeRequest.action             = TRADE_ACTION_DEAL;
        TradeRequest.symbol             = Terminal.GetSymbol();
        TradeRequest.volume             = Volume;
        TradeRequest.type               = (IsBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
        TradeRequest.price              = NormalizeDouble(Price, Terminal.GetDigits());
        TradeRequest.sl                 = NormalizeDouble((m_bContainsPosition ? 0 : Stop), Terminal.GetDigits());
        TradeRequest.tp                 = NormalizeDouble((m_bContainsPosition ? 0 : Take), Terminal.GetDigits());
        TradeRequest.type_time          = (DayTrade ? ORDER_TIME_DAY : ORDER_TIME_GTC);
        TradeRequest.stoplimit          = 0;
        TradeRequest.expiration         = 0;
        TradeRequest.type_filling       = ORDER_FILLING_RETURN;
        TradeRequest.deviation          = 1000;
        TradeRequest.comment            = "[ Order Market ] Generated by Experts Advisor.";
        if (!Send()) return 0;
                                                
        return TradeResult.order;
};


Sebbene possa sembrare strano, queste modifiche forniscono già un livello di sicurezza sufficiente (almeno un livello accettabile), che consente di non perdere un ordine OCO, pendente o a mercato quando esiste già una posizione aperta per lo stesso asset che l'EA sta tradando. Pertanto, l'EA avrà effettivamente cura del sistema di invio degli ordini.

Beh, è ​​tutto troppo bello e meraviglioso per essere vero, non è vero? Potreste pensare che questo già vi garantirà un buon margine di sicurezza, ma non è proprio così. Queste modifiche garantiscono che un ordine OCO non rimanga pendente o non entri a mercato quando abbiamo una posizione aperta. Ma c'è un errore fatale in queste modifiche e se non opportunamente corretto, questo difetto può darti un enorme mal di testa e una tremenda perdita, che può demolire il tuo conto o vedere la posizione chiusa dal broker per mancanza di margine.

Notare che non vi è alcun controllo se un ordine pendente rientra o meno nei limiti di una posizione aperta, e questo è molto pericoloso perché allo stato attuale del sistema, quando si aggiunge un ordine OCO pendente al di fuori dei limiti di una posizione aperta, l'EA non consente che tale ordine sia di tipo OCO. In altre parole: l'ordine non avrà limiti, questo ordine sarà un ordine senza take profit o stop loss, così quando chiudi una posizione e inserisci questo ordine pendente, dovrai aggiustare questi livelli il più rapidamente possibile. Se ti dimentichi di farlo, rischi di avere una posizione aperta senza limiti.

Per impostare i livelli, dovresti aprire la finestra del messaggio, aprire l'ordine e modificare i valori del livello. Questo problema verrà risolto presto. Ora, risolviamo il problema attuale.

Pertanto, dobbiamo cambiare il modo in cui l'EA lavora con gli ordini pendenti, perché se l'utente desidera creare un ordine senza i livelli, l'EA lo tratterà come qualcosa di normale, ma se l'utente finisce per creare un ordine limit, l'EA dovrà adeguare l'ordine di conseguenza: impostando dei limiti se l'ordine viene piazzato al di fuori della posizione aperta, o rimuovendo i limiti di un ordine pendente se l'ordine viene piazzato all'interno della posizione aperta.


2.0.2. Lavorare entro i limiti

La prima cosa che faremo è creare la verifica dei limiti. È molto semplice da fare. Tuttavia, richiede molta attenzione ai dettagli, perché ci sono due casi possibili che possono essere estesi a più casi. Per capire cosa fare, bastano questi due casi.


Il primo caso è mostrato sopra. Avrà un ordine pendente al di fuori dei limiti (il limite in questo caso è un'area nel gradiente), ovvero abbiamo una posizione, buy o sell, e abbiamo un limite superiore. Quando il prezzo raggiunge o supera questo limite, la posizione verrà chiusa. In questo caso, l'ordine pendente può essere configurato dall'utente come ordine OCO e l'EA deve accettare il modo in cui l'ordine è configurato dall'utente, sia esso un ordine semplice o un ordine OCO — l'EA non dovrebbe interferire in questo caso.

Il secondo caso è mostrato di seguito. Qui l'ordine pendente è all'interno dell'area delimitata dalla posizione aperta. In questo caso l'EA deve rimuovere i limiti che possono essere configurati dall'utente.


Fai attenzione che non importa quanto lontano vada il limite, se acquistiamo o vendiamo, se un’ordine entra in quest'area, l'EA deve rimuovere i valori limite dall'ordine pendente. Ma se lascia quest'area, l'EA deve lasciare l'ordine nel modo in cui è stato configurato dall'utente.

Dopo aver definito questo, dobbiamo creare alcune variabili mostrate di seguito:

class C_Router : public C_HLineTrade
{
        protected:
                MqlTradeRequest TradeRequest;
                MqlTradeResult  TradeResult;
        private  :
                bool            m_bContainsPosition;
                struct st00
                {
                        double  TakeProfit,
                                StopLoss;
                        bool    IsBuy;
                }m_Limits;

// ... Rest of the code

Ora abbiamo un modo per controllare i limiti nella fase in cui si verifica l'evento di attivazione dell'ordine OnTrade. Quindi, ancora una volta modifichiamo la funzione di aggiornamento della classe C_Router.

// Rest of the code....

//+------------------------------------------------------------------+
#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
void UpdatePosition(void)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        double          price;
        bool            bTest;
                                
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                {
                        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                }
                if ((m_bContainsPosition) && (bTest))
                {
                        ModifyOrderPendent(ul, price, 0, 0);
                        (OrderSelect(ul) ? 0 : 0);
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};
#undef macro_MAX
#undef macro_MIN
//+------------------------------------------------------------------+

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

Ora la classe gestirà le posizioni pendenti al fine di distinguere quando si trovano all'interno di un'area che non può avere ordini pendenti OCO o al di fuori di essa. Notare che questa funzione verrà richiamata a ogni cambio di stato nel sistema degli ordini. La prima cosa che farà è inizializzare correttamente le variabili.

m_Limits.StopLoss = -1;
m_Limits.TakeProfit = -1;
m_Limits.IsBuy = false;


Una volta fatto questo, verificheremo se c'è oppure no una posizione aperta. Questo può essere fatto in qualsiasi momento. Non appena avremo una posizione aperta, delimiterà la regione in cui non sarà possibile avere ordini pendenti OCO — a questo punto questo è raggiunto.

SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;


Ora possiamo controllare ciascuno degli ordini pendenti per scoprire se sono o meno all'interno dell'area limite. Una nota importante: qui dobbiamo sapere se l'ordine è un buy o un sell, poiché potremmo non avere un take profit ma avere uno stop loss. Quindi, in questo caso è necessario conoscere il tipo di posizione. Per capirlo, vedere le figure seguenti:

 

Se si tratta di una posizione sell, lo stop loss segna il limite superiore...

 

Per le posizioni buy, lo stop loss è un limite inferiore.

In altre parole, in alcuni casi gli ordini pendenti possono essere di tipo OCO se vengono piazzati ad un massimo. Negli altri casi, dovrebbero essere posizionati al di sotto del minimo. Ma ci può essere anche un altro caso in cui gli ordini pendenti possono anche essere di tipo OCO, come mostrato di seguito:

 

Ordini pendenti al di fuori dei limiti, che è un caso tipico.

Per verificarlo, usiamo il seguente frammento

price = OrderGetDouble(ORDER_PRICE_OPEN);
if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
{
        bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
        bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
}

Verificherà se ci sono posizioni aperte. In caso contrario, l'EA dovrà rispettare le impostazioni dell'ordine specificate dall'utente. Se viene trovata una posizione, verrà eseguito il seguente controllo nel seguente ordine:

  1. Se siamo short, allora il prezzo a cui verrà piazzato un ordine pendente deve essere maggiore del valore di stop loss della posizione aperta;
  2. Se siamo long, allora il prezzo a cui verrà piazzato un ordine pendente deve essere inferiore al valore di stop loss della posizione aperta;
  3. Se il sistema sta ancora accettando l'ordine come tipo OCO, faremo un ultimo test per vedere se l'ordine è fuori posizione.

Una volta fatto ciò, saremo sicuri che l'ordine pendente possa essere lasciato o meno nel modo in cui l'utente lo ha configurato, e la vita continua... Ma c'è un'ultima aggiunta prima di muoverci al passaggio successivo. In realtà, è l'ultimo controllo, che ho menzionato all'inizio dell'articolo, e questo è nel frammento qui sotto:

void UpdatePosition(void)
{

// ... Internal code...

        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, m_Limits.TakeProfit = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, m_Limits.StopLoss = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
        }
        if (AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        for (int i0 = o; i0 >= 0; i0--) if ((ul = OrderGetTicket(i0)) > 0) if (OrderGetString(ORDER_SYMBOL) == Terminal.GetSymbol())
        {
                price = OrderGetDouble(ORDER_PRICE_OPEN);
                if (m_bContainsPosition)
                {
                        if ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1)) bTest = false; else
                        {
                                bTest = ((!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
                                bTest = (bTest ? bTest : ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price)));
                        }
                        if (bTest)
                        {
                                ModifyOrderPendent(ul, price, 0, 0);
                                (OrderSelect(ul) ? 0 : 0);
                        }
                }
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_TP), HL_TAKE, true);
                SetLineOrder(ul, OrderGetDouble(ORDER_SL), HL_STOP, true);
        }
};


Nella parte evidenziata, controlliamo se il tipo di conto è HEDGING. Se lo fosse, anche se la variabile indica che dovremmo lavorare con i limiti, a questo punto indicherà che non abbiamo bisogno di lavorare con loro. Pertanto, l'EA ignorerà qualsiasi restrizione che possa sorgere e tratterà gli ordini nel modo in cui sono stati configurati dall'utente. Si tratta di un controllo molto semplice, ma deve essere fatto in questa fase per garantire che l’intero nostro sistema funzioni correttamente.


2.1.0. Regolazione del sistema di posizionamento

Sebbene la maggior parte dei problemi sia stata risolta modificando la classe oggetto C_Router, non disponiamo ancora di un sistema adeguato. C'è un'altra questione da risolvere: correggere il sistema di posizionamento tramite mouse, che è un altro passaggio altrettanto importante. Ha diverse implicazioni, dal momento che dobbiamo definire come dovrebbe funzionare il sistema presente nella classe C_OrderView.

La grande domanda, su come si vuole effettivamente operare, è se la classe C_OrderView creerà oppure no i limiti per gli ordini pendenti quando questi lasciano i limiti di una posizione aperta.

Sebbene sia allettante farlo ogni volta, ci sono cose che devono essere prese in considerazione quando si prende questa decisione. Ma andiamo per parti come direbbe Jack lo Squartatore. Fondamentalmente, l'unico vero cambiamento che dovremo effettivamente apportare nella classe C_OrderView è mostrato nel codice seguente:


inline void MoveTo(uint Key)
{
        static double local = 0;
        datetime dt;
        bool    bEClick, bKeyBuy, bKeySell, bCheck;
        double  take = 0, stop = 0, price;
                                
        bEClick  = (Key & 0x01) == 0x01;    //Left click
        bKeyBuy  = (Key & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (Key & 0x08) == 0x08;    //CTRL pressed                          
        Mouse.GetPositionDP(dt, price);
        if (bKeyBuy != bKeySell)
        {
                Mouse.Hide();
                bCheck = CheckLimits(price);
        } else Mouse.Show();
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLinePrice, 0, 0, price = (bKeyBuy != bKeySell ? price : 0));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineTake, 0, 0, take = (bCheck ? 0 : price + (m_Infos.TakeProfit * (bKeyBuy ? 1 : -1))));
        ObjectMove(Terminal.Get_ID(), m_Infos.szHLineStop, 0, 0, stop = (bCheck ? 0 : price + (m_Infos.StopLoss * (bKeyBuy ? -1 : 1))));
        if((bEClick) && (bKeyBuy != bKeySell) && (local == 0)) CreateOrderPendent(bKeyBuy, m_Infos.Volume, local = price, take, stop, m_Infos.IsDayTrade);
        local = (local != price ? 0 : local);                           
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLinePrice, OBJPROP_COLOR, (bKeyBuy != bKeySell ? m_Infos.cPrice : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineTake, OBJPROP_COLOR, (take > 0 ? m_Infos.cTake : clrNONE));
        ObjectSetInteger(Terminal.Get_ID(), m_Infos.szHLineStop, OBJPROP_COLOR, (stop > 0 ? m_Infos.cStop : clrNONE));
};

Tutto qui? Sì, è tutto quello che dobbiamo fare. Tutto il resto della logica è all'interno della classe C_Router. Ciò che non è stato modificato viene eseguito dal sistema di messaggistica di MetaTrader 5, perché quando c'è un cambiamento nell'elenco degli ordini (pendenti o posizioni), verrà chiamata la routine OnTrade. Quando questo accade, attiverà la routine di aggiornamento dall'interno della classe C_Router che farà le correzioni necessarie. Ma c'è un codice che appare in questa routine e può farti impazzire cercando dove si trova. Infatti è all'interno della classe C_Router, si vede qui sotto:

#define macro_MAX(A, B) (A > B ? A : B)
#define macro_MIN(A, B) (A < B ? A : B)
inline bool CheckLimits(const double price)
{
        bool bTest = false;
                                
        if ((!m_bContainsPosition) || ((m_Limits.StopLoss == -1) && (m_Limits.TakeProfit == -1))) return bTest;
        bTest = ((macro_MAX(m_Limits.TakeProfit, m_Limits.StopLoss) > price) && (macro_MIN(m_Limits.TakeProfit, m_Limits.StopLoss) < price));
        if (m_Limits.TakeProfit == 0)
        {
                bTest = (bTest ? bTest : (!m_Limits.IsBuy) && (m_Limits.StopLoss > price));
                bTest = (bTest ? bTest : (m_Limits.IsBuy) && (m_Limits.StopLoss < price));
        }
        return bTest;
};
#undef macro_MAX
#undef macro_MIN


Questo codice era all'interno della funzione di aggiornamento della classe C_Router. È stato rimosso da lì e sostituito con una chiamata...


2.2.0. Limitare o non limitare, questo è il problema

Il nostro lavoro è quasi terminato, ma resta da risolvere l'ultima questione, probabilmente la più difficile al momento. Se hai seguito e compreso il contenuto fino a questo punto, avrai notato che il sistema funziona molto bene, ma ogni volta che un ordine pendente configurato come OCO entra nei limiti di una posizione aperta, l'ordine perde i limiti che gli erano stati configurati . Questo accadrà sempre.

Ma se per caso il trader modifica i limiti di una posizione aperta, o se un ordine che era un OCO e ora è un ordine semplice va oltre quei limiti, sarà comunque un ordine semplice. Quindi, abbiamo un potenziale problema.

Altro grande dettaglio: come deve procedere l’EA? Dovrebbe notificare al trader che è appena comparso un ordine semplice per l'asset? O dovrebbe semplicemente impostare dei limiti per l'ordine e trasformarlo in OCO?

Questa domanda è estremamente rilevante se vuoi davvero che l'EA ti aiuti nel trading. Sarebbe bene che l'EA ci avvertisse per informarci di quanto è accaduto. Ma se ci si trova in un asset, in un momento di alta volatilità è anche bene che l'EA crei automaticamente dei limiti per l'ordine in modo che non rimanga lì sciolto, il che può causare gravi danni anche prima di rendersi conto di cosa sta succedendo .

Quindi, per risolvere questo problema, il sistema ha subito un'ultima modifica, ma come ho spiegato sopra, dovresti pensare seriamente a come affrontare effettivamente questo problema. Di seguito ecco come ho implementato una possibile soluzione.

Innanzitutto, ho aggiunto una nuova variabile attraverso la quale il trader può informare l'EA quale procedura verrà eseguita. È mostrata di seguito:

// ... Code ...

input group "Chart Trader"
input int       user20   = 1;              //Leverage factor
input int       user21   = 100;            //Take Profit (financial)
input int       user22   = 75;             //Stop Loss (financial)
input color     user23   = clrBlue;        //Price line color
input color     user24   = clrForestGreen; //Take Profit line color
input color     user25   = clrFireBrick;   //Stop Loss line color
input bool      user26   = true;           //Day Trade?
input bool      user27   = true;           //Always set loose order limits

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

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

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

Ora dobbiamo tornare alla classe C_Router e aggiungervi 3 nuove funzioni. Possono essere viste sotto.

//+------------------------------------------------------------------+
void SetFinance(const int Contracts, const int Take, const int Stop)
{
        m_Limits.Contract = Contracts;
        m_Limits.FinanceTake = Take;
        m_Limits.FinanceStop = Stop;
}
//+------------------------------------------------------------------+
inline double GetDisplacementTake(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceTake * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? 1 : -1));
}
//+------------------------------------------------------------------+
inline double GetDisplacementStop(const bool IsBuy, const double Vol) const
{
        return (Terminal.AdjustPrice(m_Limits.FinanceStop * (Vol / m_Limits.Contract) * Terminal.GetAdjustToTrade() / Vol) * (IsBuy ? -1 : 1));
}
//+------------------------------------------------------------------+

Il codice manterrà i valori che sono inseriti nel Chart Trader, come si può vedere nell'immagine successiva, ma correggerà anche proporzionalmente il valore che dovremmo usare come limiti negli ordini pendenti OCO.

Cioè, abbiamo già dove ottenere i valori che useremo in modo che l'EA possa configurare minimamente un ordine OCO quando viene attivato un ordine pendente. Tuttavia, come potresti sospettare, dovremo apportare una nuova modifica al codice di aggiornamento della classe C_Router. La modifica è mostrata di seguito:

void UpdatePosition(bool bAdjust)
{
        static int      memPositions = 0, memOrder = 0;
        ulong           ul;
        int             p, o;
        long            info;
        double          price, stop, take, vol;
        bool            bIsBuy, bTest;
                        
        p = PositionsTotal();
        o = OrdersTotal();
        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;
                m_bContainsPosition = false;
                m_Limits.StopLoss = -1;
                m_Limits.TakeProfit = -1;
                m_Limits.IsBuy = false;
        };
        for(int i0 = p; i0 >= 0; i0--) if(PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                ul = PositionGetInteger(POSITION_TICKET);
                m_bContainsPosition = true;
                SetLineOrder(ul, PositionGetDouble(POSITION_PRICE_OPEN), HL_PRICE, false);
                SetLineOrder(ul, take = PositionGetDouble(POSITION_TP), HL_TAKE, true);
                SetLineOrder(ul, stop = PositionGetDouble(POSITION_SL), HL_STOP, true);
                m_Limits.IsBuy = PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY;
                m_Limits.TakeProfit = (m_Limits.TakeProfit < 0 ? take : (m_Limits.IsBuy ? (m_Limits.TakeProfit > take ? m_Limits.TakeProfit : take) : (take > m_Limits.TakeProfit ? m_Limits.TakeProfit : take)));
                m_Limits.StopLoss = (m_Limits.StopLoss < 0 ? stop : (m_Limits.IsBuy ? (m_Limits.StopLoss < stop ? m_Limits.StopLoss : stop) : (stop < m_Limits.StopLoss ? m_Limits.StopLoss : stop)));
        }
        if ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE) == ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
                m_bContainsPosition = false;
        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);
                if ((take == 0) && (stop == 0) && (bAdjust) && (!bTest))
                {
                        info = OrderGetInteger(ORDER_TYPE);
                        vol = OrderGetDouble(ORDER_VOLUME_CURRENT);
                        bIsBuy = ((info == ORDER_TYPE_BUY_LIMIT) || (info == ORDER_TYPE_BUY_STOP) || (info == ORDER_TYPE_BUY_STOP_LIMIT) || (info == ORDER_TYPE_BUY));
                        take = price + GetDisplacementTake(bIsBuy, vol);
                        stop = price + GetDisplacementStop(bIsBuy, vol);
                        ModifyOrderPendent(ul, price, take, stop);
                }
                if ((take != 0) && (stop != 0) && (bTest))
                        ModifyOrderPendent(ul, price, take = 0, stop = 0);
                SetLineOrder(ul, price, HL_PRICE, true);
                SetLineOrder(ul, take, HL_TAKE, true);
                SetLineOrder(ul, stop, HL_STOP, true);
        }
};

Le righe evidenziate verificheranno se l'ordine è libero e se l'EA deve intervenire in esso. Se l'EA interviene, il calcolo verrà effettuato in base al valore finanziario presentato nel Chart Trader e in base al volume dell'ordine pendente. Il semplice ordine pendente riceverà quindi i limiti calcolati in base alle informazioni raccolte. L'EA creerà linee per informare che i limiti saranno creati trasformando così il semplice ordine pendente in un ordine pendente OCO.


Conclusioni

Nonostante tutti i tentativi di testare il sistema e vedere se riconosce il conto come hedging, in questa fase non ho avuto successo. L'EA ha sempre riferito che l'account era in modalità netting, anche se la piattaforma MetaTrader 5 riferiva che l'account era hedging. Pertanto, dovresti stare attento. Sebbene funzioni come volevamo, gli ordini pendenti vengono regolati anche su un conto hedging come se fosse un conto netting...

Il video mostra chiaramente tutto quanto sopra descritto. Come puoi vedere, il sistema è molto interessante da usare.




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

File allegati |
Sviluppare un Expert Advisor per il trading da zero (Parte 19): Nuovo sistema di ordini (II) Sviluppare un Expert Advisor per il trading da zero (Parte 19): Nuovo sistema di ordini (II)
In questo articolo svilupperemo un sistema di ordini grafico del tipo "guarda cosa succede". Tieni presente che questa volta non stiamo partendo da zero, ma modificheremo il sistema esistente aggiungendo più oggetti ed eventi sul grafico dell'asset che stiamo tradando.
Algoritmi di ottimizzazione della popolazione: Sciame di particelle (PSO) Algoritmi di ottimizzazione della popolazione: Sciame di particelle (PSO)
In questo articolo, prenderò in considerazione il famoso algoritmo Particle Swarm Optimization (PSO). In precedenza, abbiamo discusso caratteristiche così importanti degli algoritmi di ottimizzazione come convergenza, tasso di convergenza, stabilità, scalabilità, nonché sviluppato un banco di prova e considerato il più semplice algoritmo RNG..
Impara come progettare un sistema di trading tramite Force Index Impara come progettare un sistema di trading tramite Force Index
Benvenuti nel nostro nuovo articolo della nostra serie su come progettare un sistema di trading tramite gli indicatori tecnici più popolari. In questo articolo, impareremo a conoscere un nuovo indicatore tecnico e come creare un sistema di trading utilizzando l'indicatore Force Index.
Algoritmi di ottimizzazione della popolazione Algoritmi di ottimizzazione della popolazione
Questo è un articolo introduttivo sulla classificazione dell'algoritmo di ottimizzazione (OA). L'articolo tenta di creare un banco di prova (un insieme di funzioni), che deve essere utilizzato per confrontare gli OA e forse, identificare l'algoritmo più universale tra tutti quelli ampiamente conosciuti.