Sviluppare un Expert Advisor per il trading da zero (Parte 20): Nuovo sistema di ordini (III)
Introduzione
Nel precedente articolo, Sviluppare un Expert Advisor per il trading da zero (Parte 19), ci siamo concentrati sulle modifiche al codice che sono state implementate per consentire il funzionamento del nuovo sistema di ordini. Poiché questi cambiamenti sono stati implementati, posso concentrarmi al 100% sul vero problema. Questo per implementare il sistema degli ordini, che è visuale al 100% e comprensibile per chi fa trading senza dover conoscere il valore del tick o dove piazzare un ordine per guadagnare X, o dove impostare lo Stop Loss per non perdere Y.
La creazione di un tale sistema richiede una buona padronanza di MQL5, nonché una comprensione di come funziona effettivamente la piattaforma MetaTrader 5 e quali risorse fornisce.
1.0. Pianificazione
1.0.1. Progettare gli indicatori
L'idea qui, beh non solo l'idea ma quello che effettivamente farò, è mostrare come implementare il sistema in questo articolo. Creeremo qualcosa di simile a quanto mostrato nell'immagine qui sotto:
È molto facile da capire anche senza la mia spiegazione. C'è un pulsante di chiusura, un valore e un punto per facilitare il trascinamento e l'inserimento degli ordini. Ma non è tutto. Quando lo Stop Loss si trasforma in uno Stop Gain, il sistema lo gestirà come segue:
Quindi, possiamo facilmente sapere quando, quanto, dove e se vale o meno la pena mantenere una certa posizione.
Le figure sopra mostrano solo gli oggetti dell'ordine OCO o i limiti della posizione OCO, ma non ho dimenticato la parte relativa al prezzo di apertura, in quanto anche questo è altrettanto importante.
Per gli ordini pendenti, apparirà come segue:
Ed avrà un aspetto leggermente diverso per una posizione:
Tuttavia, le proporzioni non sono molto incoraggianti... Ma questa è l'idea che sarà implementata. Per quanto riguarda i colori, userò quelli mostrati qui. Ma puoi scegliere quelli che ti piacciono.
Mentre continuiamo a pianificare, possiamo notare che avremo fondamentalmente 5 oggetti in ciascun indicatore. Significa che MetaTrader 5 dovrà processare 5 oggetti contemporaneamente per ogni indicatore. In caso di un ordine OCO, MetaTrader 5 dovrà gestire 15 oggetti, proprio come con una posizione OCO, dove MetaTrader 5 deve gestire 15 oggetti, che sono per un ordine o posizione. Significa che se hai 4 ordini pendenti OCO e 1 posizione OCO aperta, MetaTrader 5 dovrà gestire 25 oggetti, oltre a quelli che saranno anche sul grafico. E questo è se stai utilizzando solo un asset nella piattaforma.
Dico questo perché è importante conoscere l'eventuale memoria e l'elaborazione necessaria per ogni ordine che effettuerai per lo strumento di trading. Bene, questo non è un problema per i computer moderni, ma è necessario sapere cosa esattamente chiederemo all'hardware. In precedenza, c'era un solo oggetto sullo schermo per ogni punto dell'ordine. Ora ci saranno 5 oggetti in ciascuno dei punti e dovranno in qualche modo rimanere connessi. Questa connessione sarà implementata dalla piattaforma, mentre noi istruiremo solo come dovrebbero essere connessi e cosa dovrebbe accadere quando ciascuno degli oggetti si attiva.
1.0.2. La scelta degli oggetti
La prossima domanda è la scelta degli oggetti che useremo. Potrebbe sembrare una domanda semplice, ma è molto importante in quanto determina come andrà fatta effettivamente l'implementazione. La prima scelta si basa sul modo in cui gli oggetti sono posizionati sullo schermo.
Abbiamo due modi per implementare questo. Fortunatamente, MetaTrader 5 copre entrambi: il primo utilizza il posizionamento in base alle coordinate tempo e prezzo, il secondo utilizza le coordinate cartesiane X e Y.
Tuttavia, prima di procedere con uno di essi in dettaglio, scarterò immediatamente il modello che utilizza le coordinate di tempo e prezzo. Nonostante sia ideale a prima vista, non sarà utile quando avremo a che fare con tanti oggetti che andranno connessi tra loro e che devono restare uniti. Pertanto, dovremo utilizzare il sistema cartesiano.
In uno degli articoli precedenti, abbiamo già visto questo sistema e discusso su come selezionare gli oggetti. Per i dettagli vedere Indicatori multipli su un grafico (Parte 05).
Abbiamo completato la pianificazione e ora possiamo finalmente passare alla codifica stessa: è ora di mettere in pratica le cose.
2.0. Implementazione
Il mio scopo non è semplicemente quello di implementare il sistema, ma di spiegare esattamente cosa sta succedendo in esso, in modo che anche voi possiate creare il vostro sistema basato su quello considerato, fornirò i dettagli a poco a poco. Questo ti aiuterà a capire come viene creato. Non dimenticare che qualsiasi funzionalità relativa agli ordini pendenti funzionerà anche per le posizioni, poiché il sistema segue gli stessi principi e ha un codice comune.
2.0.1. Creazione di un framework per l’interfaccia
Il risultato di questo primo passaggio può essere visto di seguito. Questo è il mio modo di presentare i vantaggi in modo da entusiasmarvi come lo ero io quando ho sviluppato e ho deciso di condividere questi codici con tutti voi. Spero che questo serva da motivazione per coloro che vogliono imparare a programmare o sviluppare una conoscenza più approfondita sull'argomento.
Guardando l'immagine qui sopra si potrebbe pensare che la funzionalità sia stata creata nel solito modo, escludendo tutto il codice creato finora. Ma no, useremo esattamente ciò che è stato costruito finora.
Quindi, utilizzeremo il codice presentato nell'articolo precedente e apporteremo alcune modifiche. Quindi, concentriamoci sulle novità del codice. Innanzitutto, aggiungeremo tre nuove classi.
2.0.1.1. Classe C_Object_Base
Inizieremo creando una nuova classe - C_Object_Base. Questa è la classe più bassa del nostro sistema. Di seguito sono riportati i primi codici della classe:
class C_Object_Base { public : //+------------------------------------------------------------------+ void Create(string szObjectName, ENUM_OBJECT typeObj) { ObjectCreate(Terminal.Get_ID(), szObjectName, typeObj, 0, 0, 0); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTABLE, false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_SELECTED, false); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, true); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n"); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BACK, false); ObjectSetString(Terminal.Get_ID(), szObjectName, OBJPROP_TOOLTIP, "\n"); PositionAxleY(szObjectName, 9999); }; // ... The rest of the class code
Notare che abbiamo un codice generale che renderà la nostra vita molto più semplice. Nella stessa classe, abbiamo codici di posizionamento X e Y standard.
void PositionAxleX(string szObjectName, int X) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XDISTANCE, X); }; //+------------------------------------------------------------------+ virtual void PositionAxleY(string szObjectName, int Y) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y); };
Il codice di posizionamento Y dipenderà da un oggetto specifico, ma anche se l'oggetto non ha un codice specifico, la classe ne fornisce uno generico. Abbiamo un modo generico per specificare il colore dell'oggetto mostrato sotto.
virtual void SetColor(string szObjectName, color cor) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, cor); }
Ed ecco il modo per definire le dimensioni degli oggetti.
void Size(string szObjectName, int Width, int Height) { ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, Width); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, Height); };
Questo è tutto per ora con la classe C_Object_Base, ma ritorneremo più tardi.
2.0.1.2. Classe C_Object_BackGround
Creiamo ora altre due classi per supportare i nostri due oggetti grafici. Il primo è C_Object_BackGround. Crea uno sfondo per ricevere altri elementi. Il suo codice è piuttosto semplice. Potete vederlo di seguito per intero:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #include "C_Object_Base.mqh" //+------------------------------------------------------------------+ class C_Object_BackGround : public C_Object_Base { public: //+------------------------------------------------------------------+ void Create(string szObjectName, color cor) { C_Object_Base::Create(szObjectName, OBJ_RECTANGLE_LABEL); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_COLOR, clrNONE); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_BGCOLOR, cor); } //+------------------------------------------------------------------+ virtual void PositionAxleY(string szObjectName, int Y) { int desl = (int)(ObjectGetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE) / 2); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y - desl); } //+------------------------------------------------------------------+ };
Notare che stiamo usando l'ereditarietà per assemblare l'oggetto con il minimo codice. Pertanto, otteniamo la classe modificandola e modellandola come necessario, in modo che in seguito non sia necessario apportare queste modifiche. Questo può essere visto nel codice evidenziato, dove la classe si posizionerà automaticamente nella posizione corretta semplicemente conoscendo il valore dell'asse Y — controllerà la dimensione e si posizionerà in modo che sia al centro dell'asse che le stiamo passando.
2.0.1.3. Classe C_Object_TradeLine
La classe C_Object_TradeLine è responsabile della sostituzione della linea orizzontale precedentemente utilizzata per indicare dove si trovava la linea del prezzo dell'ordine. Questa classe è molto interessante, quindi dai un'occhiata al suo codice: ha una variabile statica privata, come puoi vedere nel codice qui sotto.
#property copyright "Daniel Jose" #include "C_Object_BackGround.mqh" //+------------------------------------------------------------------+ class C_Object_TradeLine : public C_Object_BackGround { private : static string m_MemNameObj; public : //+------------------------------------------------------------------+ // ... Internal class code //+------------------------------------------------------------------+ }; //+------------------------------------------------------------------+ string C_Object_TradeLine::m_MemNameObj = NULL; //+------------------------------------------------------------------+
È evidenziata per mostrare come dichiararla e come inizializzarla correttamente. Bene, potremmo creare una variabile globale per sostituire ciò che farà la variabile statica, ma voglio mantenere il controllo sulle cose: in questo modo ogni oggetto ha tutto ciò di cui ha bisogno e le informazioni sono memorizzate al loro interno. E se vogliamo sostituire un oggetto con un altro, possiamo farlo facilmente.
La prossima cosa a cui prestare attenzione è il codice di creazione dell'oggetto.
void Create(string szObjectName, color cor) { C_Object_BackGround::Create(szObjectName, cor); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_XSIZE, TerminalInfoInteger(TERMINAL_SCREEN_WIDTH)); SpotLight(szObjectName); };
Per implementarlo correttamente, usiamo la classe C_Object_BackGround, in cui creiamo effettivamente un riquadro che fungerà da linea. Ancora una volta, questo perché se fosse utilizzato un altro tipo di oggetto non avremmo lo stesso comportamento che abbiamo adesso, e l'unico oggetto che fa quello di cui abbiamo bisogno è quello presente nella classe C_Object_Background. Quindi, lo modificheremo per adattarlo alle nostre esigenze e creare così una linea.
Successivamente, vedremo il codice responsabile dell'evidenziazione di una linea.
void SpotLight(string szObjectName = NULL) { if (szObjectName != NULL) ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YSIZE, (szObjectName != NULL ? 4 : 3)); if (m_MemNameObj != NULL) ObjectSetInteger(Terminal.Get_ID(), m_MemNameObj, OBJPROP_YSIZE, 3); m_MemNameObj = szObjectName; };
Questo codice è piuttosto interessante, poiché quando evidenziamo una linea non abbiamo bisogno di sapere quale è stata evidenziata, mentre l'oggetto stesso lo fa per noi. E quando una nuova linea merita di essere evidenziata, la linea che era evidenziata perde automaticamente questo stato e la nuova linea ne prende il posto. Ora, se nessuna linea deve essere evidenziata, chiamiamo semplicemente la funzione, la quale si occuperà di rimuovere l'evidenziazione da qualsiasi linea.
Sapendo questo, il codice sopra insieme al codice seguente fa sparire il vecchio codice di selezione. In questo modo MetaTrader 5 ci farà sapere quale indicatore stiamo manipolando.
string GetObjectSelected(void) const { return m_MemNameObj; }
C'è un'altra funzione a cui vale la pena prestare attenzione. Posiziona la linea lungo l'asse Y. La vedremo di seguito.
virtual void PositionAxleY(string szObjectName, int Y) { int desly = (m_MemNameObj == szObjectName ? 2 : 1); ObjectSetInteger(Terminal.Get_ID(), szObjectName, OBJPROP_YDISTANCE, Y - desly); };
Come per la funzione mostrata nell'oggetto BackGround, anche questa funzione si aggiusta il punto corretto a seconda che la linea sia evidenziata o meno.
Abbiamo due oggetti totalmente completati. Ma prima di poterli vedere effettivamente sullo schermo (come mostrato sopra), dovremo fare alcune cose nella classe C_ObjectsTrade.
2.0.2. Modifica della classe C_ObjectsTrade
Le modifiche da apportare non sono molto complicate a prima vista, ma il numero di volte che dovremo ripetere lo stesso codice a volte può essere un po' scoraggiante, quindi ho cercato di trovare un modo per aggirare questo problema. La prima cosa che faremo è creare un'enumerazione di eventi utilizzando le macro, ma se trovi confuso seguire un codice pieno di macro, sentiti libero di passare dalle macro alle funzioni o alle procedure e, in casi estremi, a sostituire le macro con il codice interno appropriato. Preferisco usare le macro, perché lo faccio da molti anni.
Innanzitutto, creiamo un'enumerazione di eventi.
enum eEventType {EV_GROUND = 65, EV_LINE};
Man mano che gli oggetti vengono creati, dobbiamo aggiungere nuovi eventi qui e devono essere qualcosa di importante. Tuttavia, ogni oggetto avrà un solo tipo di evento e quell'evento sarà generato da MetaTrader 5. Oppure il codice si assicurerà solo che l'evento sia gestito correttamente.
Fatto ciò, creeremo variabili che forniranno l'accesso a ciascuno oggetto.
C_Object_BackGround m_BackGround; C_Object_TradeLine m_TradeLine;
Sono nell'ambito globale della classe, ma sono private. Potremmo dichiararli in ogni funzione che li utilizza, ma ciò non ha molto senso poiché l'intera classe si occuperà degli oggetti.
Quindi, facciamo la modifica corrispondente nel codice dall'articolo precedente.
inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev) { return StringFormat("%s%c%c%c%d%c%c", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)ev); }
Le parti evidenziate non esistevano nella versione precedente, ma ora aiuteranno MetaTrader 5 a tenerci informati su ciò che sta accadendo.
Inoltre, abbiamo una nuova funzione.
void SetPositionMinimalAxleX(void) { m_PositionMinimalAlxeX = (int)(ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS) * 0.2); }
Crea un punto di partenza lungo l'asse X per gli oggetti. Ciascuno degli oggetti avrà un certo punto, ma qui forniamo un riferimento iniziale. Puoi modificarlo per cambiare la posizione iniziale semplicemente cambiando il punto in questo codice sopra.
La funzione di selezione ha subito molte modifiche, ma cambierà ancora un po' più avanti. Al momento, è la seguente.
inline void Select(const string &sparam) { ulong tick; double price; eIndicatorTrade it; eEventType ev; string sz = sparam; if (!GetInfosOrder(sparam, tick, price, it, ev)) sz = NULL; m_TradeLine.SpotLight(sz); }
Un'altra funzione che è cambiata è anche quella che crea l'indicatore.
inline void CreateIndicatorTrade(ulong ticket, double price, eIndicatorTrade it, bool select) { if (price <= 0) RemoveIndicatorTrade(ticket, it); else { CreateIndicatorTrade(ticket, it, select); PositionAxlePrice(price, ticket, it, -1, -1, 0, false); } }
Ma il codice sopra non è così importante. Ciò che fa davvero tutto il duro lavoro è mostrato nel codice qui sotto.
inline void CreateIndicatorTrade(ulong ticket, eIndicatorTrade it) { color cor1, cor2; string sz0; switch (it) { case IT_TAKE : cor1 = clrPaleGreen; cor2 = clrDarkGreen; break; case IT_STOP : cor1 = clrCoral; cor2 = clrMaroon; break; case IT_PENDING: default: cor1 = clrGold; cor2 = clrDarkGoldenrod; break; } m_TradeLine.Create(MountName(ticket, it, EV_LINE), cor2); if (ticket == def_IndicatorTicket0) m_TradeLine.SpotLight(MountName(ticket, IT_PENDING, EV_LINE)); m_BackGround.Create(sz0 = MountName(ticket, it, EV_GROUND), cor1); switch (it) { case IT_TAKE: case IT_STOP: m_BackGround.Size(sz0, 92, 22); break; case IT_PENDING: m_BackGround.Size(sz0, 110, 22); break; } }
In questa funzione, determiniamo i colori e la sequenza di creazione degli oggetti, oltre a determinare le loro dimensioni. Qualsiasi oggetto che viene aggiunto all'indicatore dovrebbe essere posizionato utilizzando questa funzione in modo che tutto sia centrato e sempre controllato. Se inizi creando una funzione per creare indicatori, ti ritroverai con un tipo di codice difficile da mantenere e potrebbe non avere controlli adeguati. Potresti pensare che tutto vada bene, che funzioni e lo metti su un conto reale - solo allora verrà effettivamente controllato e potresti improvvisamente realizzare che alcune cose non funzionano correttamente. Ecco un consiglio: cerca sempre di assemblare funzioni all'interno di oggetti che svolgono lo stesso lavoro; anche se all'inizio sembra inutile, col tempo avrà senso poiché controllerai sempre le cose che cambiano.
Di seguito c’è la successiva funzione modificata.
#define macroDelete(A) { \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND)); \ ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE)); \ } inline void RemoveIndicatorTrade(ulong ticket, eIndicatorTrade it = IT_NULL) { ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false); if ((it != NULL) && (it != IT_PENDING) && (it != IT_RESULT)) macroDelete(it) else { macroDelete(IT_PENDING); macroDelete(IT_RESULT); macroDelete(IT_TAKE); macroDelete(IT_STOP); } ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true); } #undef macroDelete
Come la prossima, è un po' noiosa. La funzione dovrebbe funzionare in modo che tu sia colui che seleziona ciascuno degli oggetti creati uno per uno. Ciò dovrebbe valere per ciascuno degli indicatori. Pensaci. Se non usiamo una macro che faciliti il compito, sarà un incubo. Sarebbe estremamente noioso codificare questa funzione, poiché ogni indicatore alla fine del codice avrà 5 oggetti. Sapendo che ogni set in un ordine OCO avrebbe 3 indicatori, questo ci porterebbe a dover utilizzare 15 oggetti, nel qual caso le probabilità di sbagliare (perché la differenza tra loro è solo nel nome) sarebbe enorme. Quindi, con l'aiuto di una macro, il codice viene ridotto a quanto evidenziato nel codice: codificheremo solo 5 oggetti alla fine. Ma questa è solo la prima fase per ottenere il risultato mostrato sopra.
Per completare la prima fase, abbiamo un'altra caratteristica altrettanto noiosa. Se non stessimo usando le macro, potremmo utilizzare le procedure invece delle macro. Ma abbiamo selezionato questa modalità.
#define macroSetAxleY(A) { \ m_BackGround.PositionAxleY(MountName(ticket, A, EV_GROUND), y); \ m_TradeLine.PositionAxleY(MountName(ticket, A, EV_LINE), y); \ } #define macroSetAxleX(A, B) { \ m_BackGround.PositionAxleX(MountName(ticket, A, EV_GROUND), B); \ m_TradeLine.PositionAxleX(MountName(ticket, A, EV_LINE), B); \ } inline void PositionAxlePrice(double price, ulong ticket, eIndicatorTrade it, int FinanceTake, int FinanceStop, int Leverange, bool isBuy) { double ad; int x, y; ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price, x, y); macroSetAxleY(it); macroSetAxleX(it, m_PositionMinimalAlxeX); if (Leverange == 0) return; if (it == IT_PENDING) { ad = Terminal.GetAdjustToTrade() / (Leverange * Terminal.GetVolumeMinimal()); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceTake * (isBuy ? ad : (-ad))), x, y); macroSetAxleY(IT_TAKE); macroSetAxleX(IT_TAKE, m_PositionMinimalAlxeX + 120); ChartTimePriceToXY(Terminal.Get_ID(), 0, 0, price + Terminal.AdjustPrice(FinanceStop * (isBuy ? (-ad) : ad)), x, y); macroSetAxleY(IT_STOP); macroSetAxleX(IT_STOP, m_PositionMinimalAlxeX + 220); } } #undef macroSetAxleX #undef macroSetAxleY
Se pensi che la funzione precedente fosse noiosa, dai un'occhiata a questa. Qui il lavoro verrebbe raddoppiato, ma grazie ai codici evidenziati la cosa diventa accettabile.
Bene, ci sono altri piccoli cambiamenti che dovevano avvenire, ma non vale davvero la pena menzionarli, quindi quando eseguiamo questo codice, otteniamo esattamente cosa ci aspettiamo, vale a dire l'indicazione apparsa sullo schermo.
Conclusioni
Rimane parecchio, prima che il sistema sia completato e possa visualizzare completamente l'ordine direttamente sul grafico. Ma ora dobbiamo fare tutto in una volta, poiché è necessario apportare modifiche molto significative in altri punti del codice.
Quindi, lo lasceremo per il prossimo articolo, dato che i cambiamenti saranno molto profondi. E se qualcosa va storto, dovrai tornare indietro di un passo e riprovare fino a quando sarai in grado di cambiare il sistema nel modo desiderato. In questo modo, puoi personalizzare il sistema, lasciandolo in modo che ti sia comodo. Nel prossimo articolo, avremo il sistema come mostrato di seguito:
Sembra facile da implementare, vero? Ma fidati di me, ci sono molti cambiamenti che devono essere fatti. Quindi, ci vediamo nel prossimo articolo.
Tradotto dal portoghese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/pt/articles/10497
- App di trading gratuite
- VPS Forex gratuito per 24 ore
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso