Sviluppare un Expert Advisor da zero (Parte 30): CHART TRADE come indicatore?
Introduzione
In un precedente articolo Sviluppare un EA per il trading da zero (Parte 29), abbiamo rimosso Chart Trade dall'EA. In precedenza, abbiamo fatto lo stesso con altre cose, come Volume At Price e Times & Trade, per migliorare le prestazioni e l'affidabilità dell'EA. Rimuovendo Chart Trade dall'EA, rimane solo il sistema di ordini di base. Anche se questo può sembrare insufficiente per alcuni utenti, l'EA può in realtà svolgere tutto il lavoro. Ma ci sono persone che amano entrare e uscire dalle operazioni sul mercato, ma non amano piazzarle come pendenti e aspettano che il prezzo raggiunga un certo livello per entrare o uscire dall'operazione.
Quando utilizziamo la piattaforma MetaTrader 5 con l'asset che stiamo negoziando (ne parlo perché possiamo utilizzare il sistema di ordini incrociati, di cui abbiamo parlato nella Parte 11 di questa serie), avremo accesso al QUICK TRADING con i pulsanti che piazzano gli ordini di mercato. Sono disponibili nell'angolo in alto a sinistra. Si presentano in questo modo:
Questi pulsanti funzionano come il Chart Trade di base, ma non possono essere utilizzati per il sistema di ordini incrociati, in quanto non saranno visibili. In questo caso, quindi, dovremo tornare al nostro Chart Trade. Ma non sarà più utilizzato all'interno dell'EA e non farà parte del codice dell'EA. D'ora in poi Chart Trade sarà solo un indicatore.
Perché è necessario fare così per Chart Trade? Il motivo è che l'EA dovrebbe essere responsabile solo del sistema di trading e tutto ciò che non fa parte di questo sistema dovrebbe essere in qualche modo rimosso dal codice dell'EA. Questo può sembrare insignificante ora, ma presto diventerà più chiaro, poiché sto già preparando la continuazione di questo articolo in cui spiegherò l'esatta ragione di questo fenomeno.
Si potrebbe anche fare in modo che Chart Trade venga utilizzato come script, ma questo avrebbe un inconveniente: ogni volta che si cambia il timeframe del grafico, lo script si chiuderebbe e bisognerebbe eseguirlo di nuovo manualmente.
Questo non è il caso se lo utilizziamo come indicatore. Uno dei motivi è che Chart Trade non influisce sul thread di esecuzione degli indicatori, quindi l'EA sarà libero, concentrandosi sul suo codice e solo su ciò che riguarda la gestione degli ordini e delle posizioni.
Anche se non avremo tutti i controlli e tutte le informazioni della versione originale, questo Chart Trade come indicatore è molto più semplice e funziona ancora. Il sistema MetaTrader 5 è altamente funzionale e allo stesso tempo semplice. Tuttavia, non fornisce l'accesso a determinate informazioni che avremo sul nostro Chart Trade.
Quindi, iniziamo — l'argomento sarà molto interessante.
2.0. Sviluppo dell’indicatore Chart Trade
Per costruire questo indicatore, dobbiamo apportare molte modifiche. Implementeremo queste modifiche in modo da restituire Chart Trade al grafico senza renderlo parte del codice dell'EA.
A prima vista, potrebbe sembrare che il codice mostrato di seguito sia sufficiente almeno per la compilazione di Chart Trade. Ma no, perché è intrinsecamente legato a diverse cose che dobbiamo disattivare. Non lo cancelliamo completamente, perché un giorno potremmo volerlo riportare all'interno dell'EA.
#property copyright "Daniel Jose" #property indicator_chart_window #property indicator_plots 0 //+------------------------------------------------------------------+ #include <NanoEA-SIMD\SubWindow\C_TemplateChart.mqh> //+------------------------------------------------------------------+ C_TemplateChart Chart; C_Terminal Terminal; //+------------------------------------------------------------------+ int OnInit() { Chart.AddThese("IDE(,,170, 240)"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { return rates_total; } //+------------------------------------------------------------------+
Se provate a compilare questo codice, otterrete molti errori, ma li correggeremo uno per uno per mantenere il codice compatibile con l'EA e adattarlo all'uso come indicatore.
La prima cosa da fare è isolare il sistema che crea o gestisce le sottofinestre. Poiché Chart Trade non utilizzerà queste sottofinestre, non è necessario che il codice sia incorporato nell'indicatore. È abbastanza facile da fare. È necessario modificare il file C_Chart_IDE.mqh in modo che sia così:
#ifdef def_INTEGRATION_CHART_TRADER #include <NanoEA-SIMD\SubWindow\C_SubWindow.mqh> #include <NanoEA-SIMD\Trade\Control\C_IndicatorTradeView.mqh> #else #include <NanoEA-SIMD\SubWindow\C_ChartFloating.mqh> #include <NanoEA-SIMD\Auxiliar\C_Terminal.mqh> #endif //+------------------------------------------------------------------+ #ifdef def_INTEGRATION_CHART_TRADER class C_Chart_IDE : public C_SubWindow #else class C_Chart_IDE : public C_ChartFloating #endif
In questo modo, isoliamo completamente il sistema dalle finestre secondarie, evitando qualsiasi connessione tra Chart Trade e il sistema di visualizzazione degli ordini dell'EA. Notare che questo sarà controllato dalla definizione def_INTEGRATION_CHART_TRADER ma poiché questa definizione viene utilizzata solo nell'EA, qualsiasi cosa al suo interno non verrà compilata nell'indicatore.
Dal momento che l'indicatore non utilizzerà le sottofinestre, dovremo aggirare alcune cose. Uno di questi è mostrato di seguito:
bool Create(bool bFloat) { m_CountObject = 0; if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false; FileReadInteger(m_fp, SHORT_VALUE); for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = ""; m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA()); m_szLine = ""; while (m_szLine != "</chart>") // ... Rest of the code...
La funzione GetIdSubWinEA restituisce il numero della finestra in cui si trova l'indicatore. Questa chiamata viene eseguita in più punti diversi. Le soluzioni sono due. La prima soluzione consiste nel modificare la funzione di cui sopra in ogni punto in cui si verifica la chiamata.
bool Create(bool bFloat) { m_CountObject = 0; if ((m_fp = FileOpen("Chart Trade\\IDE.tpl", FILE_BIN | FILE_READ)) == INVALID_HANDLE) return false; FileReadInteger(m_fp, SHORT_VALUE); for (m_CountObject = eRESULT; m_CountObject <= eEDIT_STOP; m_CountObject++) m_ArrObject[m_CountObject].szName = ""; #ifdef def_INTEGRATION_CHART_TRADER m_SubWindow = ((m_IsFloating = bFloat) ? 0 : GetIdSubWinEA()); #else m_SubWindow = 0; #endif // ... The rest of the code...
Questo risolverebbe il problema, ma poi dovremmo apportare così tante modifiche che dopo un po' il codice diventerebbe troppo difficile da capire, perché ci sarebbero troppe direttive di compilazione condizionale. Ho una soluzione molto più semplice, ma altrettanto efficace: per "emulare" questa e qualsiasi altra chiamata attraverso la definizione, possiamo semplicemente aggiungere il seguente frammento nel codice del sistema.
#ifndef def_INTEGRATION_CHART_TRADER #define GetIdSubWinEA() 0 #define ExistSubWin() false #endif
Questo codice risolve il problema della chiamata. Questo sarà sufficiente (con qualche dettaglio in più) per rendere l'indicatore compilabile.
Il secondo problema più grande è legato alle funzioni coinvolte negli eventi del mouse, non il mouse stesso ma la classe C_Mouse.
La cosa da capire è che quando Chart Trade faceva parte dell'EA, era possibile accedere alle posizioni del mouse (tra le altre cose), ma se aggiungiamo semplicemente la classe C_Mouse all'indicatore, allora avremo un conflitto di interessi tra l'indicatore e l'EA. Non mostrerò ora come si risolve il conflitto. Prenderemo una direzione diversa, ma solo temporaneamente, finché non sarà tutto risolto e finché non otterremo almeno alcune funzionalità del Chart Trade originale.
A tale scopo, dobbiamo spostare alcune cose dalla classe C_Mouse al nostro nuovo indicatore. Ma non preoccupatevi, è una cosa da poco. La prima modifica avviene nella classe C_TemplateChart — dove si apporta la seguente modifica alla funzione DispatchMessage:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { datetime dt; double p; C_Chart_IDE::DispatchMessage(id, lparam, dparam, sparam); switch (id) { case CHARTEVENT_MOUSE_MOVE: #ifdef def_INTEGRATION_CHART_TRADER Mouse.GetPositionDP(dt, p); #else { int w; ChartXYToTimePrice(Terminal.Get_ID(), (int)lparam, (int)dparam, w, dt, p); } #endif for (int c0 = 0; c0 < m_Counter; c0++) if (m_Info[c0].szVLine != "") // ... Rest of the code...
Aggiungendo la sezione evidenziata, otteniamo già la funzionalità come se la classe C_Mouse esistesse ancora. La prossima modifica sarà nella classe C_ChartFloating, dove realizzeremo qualcosa di simile a quello che avevamo prima. Ma il codice è un po' diverso:
void DispatchMessage(int id, long lparam, double dparam, string sparam) { int mx, my; datetime dt; double p; static int six = -1, siy = -1, sic = -1; switch (id) { case CHARTEVENT_MOUSE_MOVE: #ifdef def_INTEGRATION_CHART_TRADER Mouse.GetPositionXY(mx, my); if ((Mouse.GetButtonStatus() & 0x01) == 1) #else mx = (int)lparam; my = (int)dparam; if (((uint)sparam & 0x01) == 1) #endif { if (sic == -1) for (int c0 = m_MaxCounter - 1; (sic < 0) && (c0 >= 0); c0--) // ... Rest of the code...
Ora possiamo compilare l'indicatore e ottenere il risultato mostrato nel video seguente:
Anche se l'EA non è ancora completamente funzionante, dobbiamo prendere la seguente decisione: o Chart Trade avrà le stesse capacità dell'EA o potranno essere ridotte per avere qualcosa a metà tra quello che era prima e quello che è attualmente fornito in MetaTrader 5. Poiché la mia posizione è piuttosto radicale, lascerò Chart Trade con quasi le stesse caratteristiche che aveva nell'EA. Se lo desiderate, potete ridurli.
Dopo aver preso questa decisione, possiamo passare all'argomento successivo, poiché Chart Trade è appena diventato un indicatore.
2.1. Come rendere funzionale l'indicatore Chart Trade
Ora le cose si faranno più difficili. Vediamo quindi come rendere funzionale questo indicatore, in modo da poter inviare ordini, chiudere posizioni e persino riportare i risultati delle transazioni. In realtà, non è così difficile come sembra a prima vista, perché la piattaforma MetaTrader 5 offre un percorso da seguire, in modo da poterlo fare con il minimo sforzo.
Qui faremo come nella Parte 16, in cui abbiamo utilizzato alcune caratteristiche di MetaTrader 5 per creare un sistema client-server interno (all'interno della piattaforma) per trasferire i dati tra i diversi processi. Qui faremo qualcosa di simile, solo la simulazione sarà leggermente diversa, poiché abbiamo bisogno di una comunicazione bidirezionale, che deve rimanere invisibile all'utente.
Il metodo che utilizzeremo non è l'unico possibile. Esistono altri modi, come l'utilizzo di una DLL per consentire questo trasferimento. Tuttavia, utilizzeremo le variabili di MetaTrader 5 in quanto sono di gran lunga le più facili da navigare, mantenere e modificare secondo le necessità.
Ora che abbiamo deciso di intraprendere questa strada, c'è la seconda decisione importante: chi sarà il server e chi il client? Questa decisione influenzerà come il sistema verrà effettivamente implementato. In ogni caso, avremo già il nostro protocollo di messaggistica, la cui implementazione è mostrata di seguito:
#property copyright "Daniel Jose" //+------------------------------------------------------------------+ #define def_GlobalVariableLeverage (_Symbol + "_Leverage") #define def_GlobalVariableTake (_Symbol + "_Take") #define def_GlobalVariableStop (_Symbol + "_Stop") #define def_GlobalVariableResult (_Symbol + "_Result") #define def_GlobalVariableButton (_Symbol + "_ButtonState") //+------------------------------------------------------------------+ #define def_ButtonDTSelect 0x01 #define def_ButtonSWSelect 0x02 #define def_ButtonBuyMarket 0x04 #define def_ButtonSellMarket 0x08 #define def_ButtonClosePosition 0x10 //+------------------------------------------------------------------+
Questo è tutto. In realtà non è necessario stabilire chi sarà il client e chi il server, poiché abbiamo già definito il protocollo dei messaggi. Questo protocollo deve essere definito fin dall'inizio, perché in questo modo sarà più facile sviluppare tutto il resto.
Notare che useremo 5 variabili per ogni asset per il quale è presente il set Chart Trade EA. I nomi delle variabili dipenderanno dall'asset a cui il set è collegato, in modo da poter utilizzare il set per più asset contemporaneamente.
Ecco una domanda importante: chi farà il lavoro del server? Sarà responsabile della creazione di tali variabili. Personalmente, trovo più pratico utilizzare l'EA come server e lasciare Chart Trade come client. L'idea è di avere sempre l'EA sul grafico, mentre Chart Trade sarà presente in momenti specifici quando necessario. Pertanto, l'EA sarà responsabile della creazione di 4 variabili su 5, poiché una di esse sarà responsabile di comunicare quale pulsante è stato premuto. Quindi, questa è la responsabilità di Chart Trade.
Sulla base di tutto ciò, il flusso di dati appare come segue:
- L'EA creerà delle variabili globali che conterranno i valori iniziali indicando la leva, il take profit e lo stop loss. Alla fine, verrà creata anche la variabile che informerà sul risultato del giorno, in modo che Chart Trade possa mostrarlo all'utente e non ci sia bisogno di cercare queste informazioni altrove.
- Chart Trade creerà una variabile che rappresenta il valore della pressione del pulsante. Questa variabile indica all'EA cosa fare, ad esempio chiudere una posizione aperta o eseguire un acquisto o una vendita sul mercato. Questa variabile esisterà solo durante questo periodo e cesserà di esistere non appena la richiesta sarà completata dall'EA.
Il flusso è semplice, ma garantisce che Chart Trade possa interagire con l'EA, compresa la capacità del sistema di inviare ordini e chiudere posizioni, come avveniva in precedenza.
Per garantire che Chart Trade esisterà sul grafico solo quando l'EA è presente, è necessario aggiungere alcuni controlli. Non ha senso avere Chart Trade sul grafico se l'EA non è disponibile per inviare ordini. Questi controlli comprendono i due momenti seguenti:
- La prima viene eseguita quando l'indicatore viene inizializzato
- La seconda viene eseguita quando si verifica un qualsiasi evento sul grafico.
Vediamo questo nel codice, così sarà più facile capirne il processo. Durante l'inizializzazione, viene eseguito il seguente codice:
#define def_SHORTNAME "CHART TRADE" //+------------------------------------------------------------------+ int OnInit() { long lparam = 0; double dparam = 0.0; string sparam = ""; IndicatorSetString(INDICATOR_SHORTNAME, def_SHORTNAME); if(!GlobalVariableGet(def_GlobalVariableLeverage, dparam)) return INIT_FAILED; Terminal.Init(); Chart.AddThese("IDE(,,170, 215)"); Chart.InitilizeChartTrade(dparam * Terminal.GetVolumeMinimal(), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), true); OnChartEvent(CHARTEVENT_OBJECT_ENDEDIT, lparam, dparam, sparam); return INIT_SUCCEEDED; }
La riga evidenziata valuterà una delle variabili globali, in questo caso quella che indica il livello di leva. Se questa variabile manca, l'inizializzazione fallirà. Ricordate che l'EA è responsabile della creazione di questa variabile, non Chart Trade, quindi in questo modo l'indicatore saprà se l'EA è presente o meno sul grafico e quando l'EA completerà il suo lavoro, rimuoverà questa variabile da MetaTrader 5, obbligando anche Chart Trade a cancellarla. Questo avviene nel secondo punto, dove controlliamo la stessa condizione — se la variabile globale della leva è disponibile o meno.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED); Chart.DispatchMessage(id, lparam, dparam, sparam); }
Il punto sopra indicato sarà implementato con una frequenza relativamente alta. Sebbene sia allettante inserire un sistema di eventi OnTime in un indicatore, ciò non è consigliabile: tutti gli indicatori utilizzano lo stesso filo conduttore operativo e l'inserimento di un evento OnTime in un indicatore si ripercuoterà su tutti gli altri, se non viene fatto con estrema attenzione.
Se si ritiene che il controllo di cui sopra disturbi le prestazioni generali della piattaforma, è possibile inserirlo all'interno di un evento OnTime, ma bisogna essere consapevoli delle conseguenze.
Indipendentemente da chi attiva la rimozione dell'indicatore Chart Trade dal grafico, questo avverrà nello stesso punto, come si può vedere di seguito:
void OnDeinit(const int reason) { if (reason == REASON_INITFAILED) { Print("Unable to use Chart Trade. The EA is not on the chart of this asset..."); if (!ChartIndicatorDelete(0, 0, def_SHORTNAME)) Print("Unable to delete Chart Trade from the chart."); } }
La linea selezionata rimuoverà l'indicatore dal grafico. In caso di fallimento, verrà visualizzato un messaggio pertinente. Questi messaggi sono visibili nella casella degli strumenti, come mostrato di seguito:
In questo modo, l'utente deve sempre tenere conto di tutte le informazioni presenti in questa finestra.
Manca un'ultima funzione prima di passare a un'analisi più approfondita delle modifiche apportate alla classe C_Chart_IDE. La funzione è la seguente:
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { double value; if (GlobalVariableGet(def_GlobalVariableResult, value)) { GlobalVariableDel(def_GlobalVariableResult); Chart.DispatchMessage(CHARTEVENT_CHART_CHANGE, 0, value, C_Chart_IDE::szMsgIDE[C_Chart_IDE::eROOF_DIARY]); } return rates_total; }
Questa funzione controlla le variabili globali. Pertanto, di volta in volta, quando si chiude una posizione, l'EA creerà una variabile globale, il cui nome è definito in def_GlobalVariableResult. Una volta creata questa variabile e il suo nome corrisponde con quello osservato da Chart Trade, il valore di questa variabile verrà catturato e la variabile verrà immediatamente cancellata. Questo per evitare che l'EA invii l'aggiornamento prima del completamento dell'elaborazione, causando la perdita dell'aggiornamento. Tuttavia, catturando il valore prima della cancellazione, possiamo inviarlo alla classe Chart Trade responsabile della gestione dei messaggi, in modo che il valore passato dall'EA venga mostrato in Chart Trade il prima possibile.
Questo completa la parte iniziale in cui rendiamo funzionale l'indicatore Chart Trade. La seconda parte riguarda i pulsanti. Dobbiamo anch’essi renderli funzionali. Questo può essere fatto facilmente nella funzione di elaborazione dei messaggi della funzione C_Chart_IDE:
// ... Previous code ... case CHARTEVENT_OBJECT_CLICK: if (StringSubstr(sparam, 0, StringLen(def_HeaderMSG)) != def_HeaderMSG) { Resize(-1); return; } sparam = StringSubstr(sparam, 9, StringLen(sparam)); StringToUpper(sparam); #ifdef def_INTEGRATION_CHART_TRADER if ((sparam == szMsgIDE[eBTN_SELL]) || (sparam == szMsgIDE[eBTN_BUY])) TradeView.ExecuteOrderInMarket(m_BaseFinance.Leverange, m_BaseFinance.FinanceTake, m_BaseFinance.FinanceStop, sparam == szMsgIDE[eBTN_BUY], m_BaseFinance.IsDayTrade); if (sparam == szMsgIDE[eBTN_CANCEL]) { TradeView.CloseAllsPosition(); ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false); } #else { union u00 { double Value; ulong c; }u_local; u_local.c = 0; if (sparam == szMsgIDE[eBTN_BUY]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonBuyMarket; else if (sparam == szMsgIDE[eBTN_SELL]) u_local.c = (m_BaseFinance.IsDayTrade ? def_ButtonDTSelect : def_ButtonSWSelect) + def_ButtonSellMarket; else if (sparam == szMsgIDE[eBTN_CANCEL]) { u_local.c = def_ButtonClosePosition; ObjectSetInteger(Terminal.Get_ID(), m_ArrObject[eBTN_CANCEL].szName, OBJPROP_STATE, false); } if (u_local.Value > 0) GlobalVariableSet(def_GlobalVariableButton, u_local.Value); } #endif if (sparam == szMsgIDE[eCHECK_DAYTRADE]) InitilizeChartTrade(0, 0, 0, m_BaseFinance.IsDayTrade ? false : true); break; //... Rest of the code...
Notare che il frammento contiene due codici. Il codice blu viene utilizzato quando Chart Trade è incorporato nell'EA. Quello verde viene utilizzato quando Chart Trade è presente come indicatore.
Siamo interessati al codice verde. Creerà una variabile globale e imposterà gli stati dei pulsanti. Pertanto, se un trader chiude una posizione, allora il valore corrispondente al pulsante di chiusura verrà inserito nella variabile. Ma se si invia un ordine di mercato, questo valore sarà differente e sarà ora una combinazione di altri due valori: uno indica se l'ordine è di Acquisto o di Vendita, mentre l'altro indica se si vuole effettuare un'operazione giornaliera (Day Trade) o una più lunga. Questo è tutto ciò che Chart Trade dirà all'EA.
IMPORTANTE: Questo sistema è auto-esclusivo, vale a dire che se si fa clic sul pulsante Sell e poi sul pulsante Buy prima che l'EA faccia qualcosa, l'EA effettivamente acquisterà perché il valore che indicava la vendita sarà perso a causa del nuovo valore di acquisto. Inoltre, se si richiede di vendere o acquistare mentre c'è già una posizione aperta e se si preme Cancel prima che l'EA effettui la relativa operazione, la posizione verrà chiusa.
Ora possiamo passare al codice EA per vedere come funziona nel nuovo modello.
2.2. Modifica del codice dell'EA per ricevere i messaggi da Chart Trade
Questa parte è piuttosto semplice, poiché tutto quello che dobbiamo fare è regolare alcuni piccoli dettagli. Tuttavia, ricordate quanto segue: dopo aver caricato l'EA e Chart Trade, non modificate le variabili globali o i dati contenuti nell'EA. Utilizzate il sistema di ordini o Chart Trade stesso, altrimenti potreste avere problemi. Alcuni problemi hanno una soluzione, altri no. Quindi, nel caso utilizzaste gli strumenti a disposizione, non cercate di complicarvi la vita.
Nell'articolo precedente (Parte 29), pur promuovendo la rimozione di Chart Trade, abbiamo apportato alcune modifiche che in parte verranno annullate. Non sarà necessario modificare nient'altro in relazione a questa domanda. Ma, come già detto, alcune cose sono risolvibili e altre no, quindi nel prossimo argomento di questo articolo elimineremo alcuni piccoli problemi nel rapporto tra Chart Trade e l'Expert Advisor.
Vediamo innanzitutto cosa dovremo annullare e attivare nell'EA in modo che ci sia un certo livello di comunicazione tra l'EA e Chart Trade.
Per prima cosa, modifichiamo quanto segue:
input int user20 = 1; //Leverage input double user21 = 100; //Take Profit input double user22 = 81.74; //Stop Loss input bool EA_user23 = true; //Day Trade ?
Il valore che indica se l'EA preferirà aprire operazioni lunghe o corte rimane invariato. Questo dovrà essere fatto in Chart Trade o in un ordine pendente da piazzare sul grafico. Vi ho già mostrato come farlo in articoli precedenti, quindi passiamo al lavoro di codifica. Aggiungiamo le seguenti modifiche all'evento OnInit:
int OnInit() { if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Sound.PlayAlert(C_Sounds::TRADE_ALLOWED); return INIT_FAILED; } Terminal.Init(); #ifdef def_INTEGRATION_TAPE_READING VolumeAtPrice.Init(user32, user33, user30, user31); TimesAndTrade.Init(user41); EventSetTimer(1); #endif Mouse.Init(user50, user51, user52); #ifdef def_INTEGRATION_CHART_TRADER static string memSzUser01 = ""; if (memSzUser01 != user01) { Chart.ClearTemplateChart(); Chart.AddThese(memSzUser01 = user01); } Chart.InitilizeChartTrade(EA_user20 * Terminal.GetVolumeMinimal(), EA_user21, EA_user22, EA_user23); TradeView.Initilize(); OnTrade(); #else GlobalVariableTemp(def_GlobalVariableLeverage); GlobalVariableTemp(def_GlobalVariableTake); GlobalVariableTemp(def_GlobalVariableStop); GlobalVariableTemp(def_GlobalVariableResult); GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal()); GlobalVariableSet(def_GlobalVariableTake, user21); GlobalVariableSet(def_GlobalVariableStop, user22); TradeView.Initilize(); GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof()); #endif return INIT_SUCCEEDED; }
Come si può vedere, qui aggiungiamo le variabili globali che saranno utilizzate per la comunicazione. Come abbiamo già detto, l'EA deve sempre partire prima di Chart Trade, altrimenti non saremo in grado di inizializzare l'indicatore. Notare che i valori che verranno inizialmente utilizzati da Chart Trade sono specificati nell'EA. L'inizializzazione sarà completata, anche se ci sono stati trade precedenti: il valore accumulato viene trasferito nuovamente a Chart Trade.
Prestate attenzione a un dettaglio importante: le variabili create sono di tipo temporaneo, poiché non vogliamo che vengano salvate in caso di dump dei dati dell’EA, in quanto non sono più utili dopo un certo periodo. Anche se dovesse succedere qualcosa e la piattaforma dovesse chiudersi, queste variabili non esisterebbero più.
Un'altra aggiunta da implementare è mostrata di seguito:
void OnDeinit(const int reason) { Mouse.Destroy(); TradeView.Finish(); #ifndef def_INTEGRATION_CHART_TRADER GlobalVariableDel(def_GlobalVariableLeverage); GlobalVariableDel(def_GlobalVariableTake); GlobalVariableDel(def_GlobalVariableStop); GlobalVariableDel(def_GlobalVariableResult); GlobalVariableDel(def_GlobalVariableButton); #endif #ifdef def_INTEGRATION_TAPE_READING EventKillTimer(); #endif }
Anche se le variabili sono temporanee, chiediamo ancora all'EA di rimuoverle forzatamente. Questo garantirà che Chart Trade non rimarrà più sul grafico. C'è un piccolo problema con questo evento OnDeinit, ma lo affronteremo nel prossimo argomento. Vediamo ora un altro punto piuttosto interessante. Questo può essere fatto in due modi diversi, mentre i risultati saranno QUASI identici. Dico QUASI identici, perché ci sono differenze minime che potrebbero fare la differenza. (Scusate il gioco di parole)
Chart Trade ha alcuni pulsanti che inviano messaggi all'EA attraverso una variabile globale. Ecco la funzione all'interno dell'EA che risponde adeguatamente a questi clic:
inline void ChartTrade_ClickButton(void) { union u00 { double Value; ulong c; }u_local; if (GlobalVariableGet(def_GlobalVariableButton, u_local.Value)) { GlobalVariableDel(def_GlobalVariableButton); if (u_local.c == def_ButtonClosePosition) TradeView.CloseAllsPosition(); else TradeView.ExecuteOrderInMarket(GlobalVariableGet(def_GlobalVariableLeverage), GlobalVariableGet(def_GlobalVariableTake), GlobalVariableGet(def_GlobalVariableStop), ((u_local.c & def_ButtonBuyMarket) == def_ButtonBuyMarket), ((u_local.c & def_ButtonDTSelect) == def_ButtonDTSelect)); TradeView.Initilize(); } }
La funzione è dichiarata come inline cioè deve essere inserita dal compilatore nella posizione in cui è dichiarata. Questo renderà il funzionamento il più rapido possibile.
Un dettaglio molto importante: dove posizionare questa funzione? Pensiamoci bene. Abbiamo un controllo qui, in modo che non funzioni sempre. Abbiamo quindi due possibilità. La prima consiste nel collocare la funzione all'interno dell'evento OnTick, la seconda all'interno dell'evento OnTime. La selezione deve essere basata su una certa logica, altrimenti possono sorgere problemi.
Quindi riflettiamo: Se inseriamo questa funzione nell'evento OnTick, verrà eseguita ad ogni nuovo tick proveniente dal server di trading nella piattaforma. Questa sembra una buona soluzione, poiché la funzione non verrà eseguita molte volte, ma solo in determinati momenti. Ma questo crea un problema: nel caso in cui la volatilità dell'asset che stiamo negoziando sia molto bassa, anche la frequenza degli eventi OnTick sarà molto bassa e questo significa che potremmo avere problemi con l'attivazione dell'evento clic su Chart Trade.
Ecco la seconda opzione. La collocazione della funzione in OnTime garantisce l'esecuzione della funzione, poiché l'evento OnTime si attiverà con una certa regolarità. Ma quando facciamo questo, dobbiamo ricordarci che non potremo più usare l'evento OnTime per nient'altro, se non per osservare le variabili globali.
In questa fase, abbiamo fatto un'ottima scelta, poiché l'EA osserverà solo il mercato. Nei prossimi articoli vedrete che questo diventerà sempre più evidente. Allora è una buona idea inserire una funzione all'interno dell'evento OnTime. Ma ora la domanda è: l'evento OnTime si attiva solo ogni secondo, vero?
Effettivamente, il più delle volte si attiva una volta al secondo, ma è possibile impostare un periodo più breve utilizzando la funzione EventSetMillisecondTimer. In questo modo, possiamo lanciare l'evento in meno di 1 secondo. Credo che 500 ms siano sufficienti per la maggior parte dei casi, per cui avremo la seguente riga nell'evento OnInit dell'EA:
#else GlobalVariableTemp(def_GlobalVariableLeverage); GlobalVariableTemp(def_GlobalVariableTake); GlobalVariableTemp(def_GlobalVariableStop); GlobalVariableTemp(def_GlobalVariableResult); GlobalVariableSet(def_GlobalVariableLeverage, user20 * Terminal.GetVolumeMinimal()); GlobalVariableSet(def_GlobalVariableTake, user21); GlobalVariableSet(def_GlobalVariableStop, user22); TradeView.Initilize(); GlobalVariableSet(def_GlobalVariableResult, TradeView.GetFinanceRoof()); EventSetMillisecondTimer(500); #endif
Non dimenticate di chiudere questo evento nella funzione OnDeinit, per la quale è sufficiente aggiungere la seguente riga nell'evento:
void OnDeinit(const int reason) { EventKillTimer();
Vediamo ora come si presenta l'evento OnTime. Si tratta di un codice molto semplice, dove solo la riga evidenziata viene effettivamente compilata.
void OnTimer() { #ifndef def_INTEGRATION_CHART_TRADER ChartTrade_ClickButton(); #endif #ifdef def_INTEGRATION_TAPE_READING VolumeAtPrice.Update(); TimesAndTrade.Connect(); #endif }
Tutto qui? No, c'è un altro piccolo problema. Ricordiamo che abbiamo modificato le variabili di inizializzazione dell'EA e vogliamo recuperare i dati da Chart Trade ogni volta che un nuovo ordine pendente viene inserito sul grafico. A tale scopo, è necessario aggiungere un piccolo dettaglio nel codice della classe C_IndicatorTradeView. È mostrato di seguito:
case CHARTEVENT_MOUSE_MOVE: Mouse.GetPositionDP(dt, price); mKeys = Mouse.GetButtonStatus(); bEClick = (mKeys & 0x01) == 0x01; //Left mouse button click bKeyBuy = (mKeys & 0x04) == 0x04; //SHIFT pressed bKeySell = (mKeys & 0x08) == 0x08; //CTRL pressed if (bKeyBuy != bKeySell) { if (!bMounting) { #ifdef def_INTEGRATION_CHART_TRADER m_Selection.bIsDayTrade = Chart.GetBaseFinance(m_Selection.vol, valueTp, valueSl); #else m_Selection.vol = GlobalVariableGet(def_GlobalVariableLeverage) * Terminal.GetVolumeMinimal(); valueTp = GlobalVariableGet(def_GlobalVariableTake); valueSl = GlobalVariableGet(def_GlobalVariableStop); m_Selection.bIsDayTrade = EA_user23; #endif
I codici evidenziati ora catturano i valori delle variabili globali, quindi qualsiasi cosa sia presente in Chart Trade verrà inserita nel sistema degli ordini. L'unico dettaglio è che tutti gli ordini pendenti seguiranno la tempistica specificata dall'EA, ma questo può essere modificato direttamente sul grafico. Per ulteriori dettagli vedere Sviluppare un Expert Advisor da zero (Parte 27), in cui ho mostrato come modificare gli ordini pendenti direttamente sul grafico, senza dover passare per Chart Trade.
C'è anche una piccola modifica al sistema audio. I suoni sono stati aggiunti nella classe C_Router per garantire la notifica dell'apertura e della chiusura delle posizioni se si lavora tramite Chart Trade. Se si desidera rimuovere o modificare qualcosa nel sistema audio, è necessario ricordare che ci sono altri punti da considerare.
Prima di terminare, ricordiamo che l'evento OnDeinit presenta un problema che stavamo per risolvere. Passiamo quindi all'argomento successivo e risolviamo questo problema.
2.3. Evitare che Chart Trade esca prematuramente dal grafico
Quando facciamo qualcosa, come cambiare il timeframe del grafico o i parametri dell'EA (può essere qualsiasi cosa), MetaTrader 5 genera un evento OnDeinit. L'innesco di questo evento significa che è necessario rianalizzare le cose per assicurarsi che tutto continui a funzionare come dovrebbe.
Nella maggior parte dei casi, l'attivazione di questo evento non causa alcun problema. Ma poiché creiamo un sistema client-server e usiamo variabili globali per scoprire se il server (l'EA) ha smesso di funzionare, dovremo capire come aggirare certe situazioni.
La funzione che gestisce l'evento OnDeinit originale è simile a questa:
void OnDeinit(const int reason) { EventKillTimer(); Mouse.Destroy(); TradeView.Finish(); #ifndef def_INTEGRATION_CHART_TRADER GlobalVariableDel(def_GlobalVariableLeverage); GlobalVariableDel(def_GlobalVariableTake); GlobalVariableDel(def_GlobalVariableStop); GlobalVariableDel(def_GlobalVariableResult); GlobalVariableDel(def_GlobalVariableButton); #endif #ifdef def_INTEGRATION_TAPE_READING EventKillTimer(); #endif }
La riga evidenziata rimuoverà la variabile globale, esattamente quella che utilizziamo nell'indicatore Chart Trade per verificare se l'EA è sul grafico o meno. Questo controllo viene eseguito nel codice seguente:
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if (!GlobalVariableCheck(def_GlobalVariableLeverage)) OnDeinit(REASON_INITFAILED); Chart.DispatchMessage(id, lparam, dparam, sparam); }
Cioè, ogni volta che nell'EA accade qualcosa che attiva l'evento OnDeinit, questa variabile verrà cancellata. E prima che l'EA crei nuovamente la variabile, l'indicatore potrebbe essere già stato rimosso dal grafico, il che può essere davvero strano. In alcuni momenti accadrà e in altri no. Quindi, dobbiamo in qualche modo assicurarci che tutto funzioni come ci si aspetti.
Ho trovato una soluzione a questo problema nella documentazione. La si può vedere nella sezione Codici dei motivi di deinizializzazione. Osservando questi codici, possiamo configurare la funzione di gestione dell'evento OnDeinit in modo che le variabili non vengano eliminate finché l'EA non viene rimosso dal grafico.
La soluzione e il nuovo codice di elaborazione saranno quindi i seguenti:
void OnDeinit(const int reason) { EventKillTimer(); Mouse.Destroy(); TradeView.Finish(); #ifndef def_INTEGRATION_CHART_TRADER switch (reason) { case REASON_CHARTCHANGE: break; default: GlobalVariableDel(def_GlobalVariableLeverage); GlobalVariableDel(def_GlobalVariableTake); GlobalVariableDel(def_GlobalVariableStop); GlobalVariableDel(def_GlobalVariableResult); GlobalVariableDel(def_GlobalVariableButton); }; #endif #ifdef def_INTEGRATION_TAPE_READING EventKillTimer(); #endif }
Ora, se cambiamo solo il timeframe o il modo di tracciare del grafico, non avremo più l'inconveniente associato alla scomparsa dell'indicatore Chart Trade dal grafico. In qualsiasi altra situazione può essere eliminato dal grafico, poiché non abbiamo la reale certezza che tutto si risolva. Per questo motivo, una volta caricati l'EA e Chart Trade, non avrà più senso modificare i parametri dell'EA.
Conclusioni
Vedete cosa può fare un po' di creatività? A volte dobbiamo risolvere problemi che sembrano irrisolvibili. Ma studiando la documentazione si può trovare la soluzione, quindi è fondamentale controllare sempre la documentazione e comprenderla per poter mettere in pratica le idee.
Tradotto dal portoghese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/pt/articles/10653
- App di trading gratuite
- 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