
Manuale MQL5: Gestione di eventi grafici personalizzati
Introduzione
Questo articolo è una continuazione logica dell'articolo Manuale MQL5: Gestione di eventi grafici tipici. Copre i metodi di lavoro con eventi grafici personalizzati. Qui il lettore può trovare esempi di sviluppo e gestione di eventi personalizzati. Tutte le idee discusse in questo articolo sono state implementate con strumenti orientati agli oggetti.
Poiché il tema degli eventi personalizzati è piuttosto ampio, è il caso in cui un programmatore e uno sviluppatore possono introdurre la creatività nel loro lavoro.
1. Evento grafico personalizzato
È chiaro che questo evento è definito dall'utente. Spetta a un programmatore decidere cosa esattamente e quale compito o blocco di programma potrebbe assumere la forma di un evento. Gli sviluppatori MQL5 possono creare i propri eventi, che espandono le capacità del linguaggio per l'implementazione di algoritmi complessi.
Un evento personalizzato è il secondo tipo possibile di evento grafico. Il primo è un evento tipico. Sebbene non esista un termine come "evento grafico tipico" nella documentazione, suggerisco comunque di utilizzarlo per i primi dieci tipi di eventi grafici.
Gli sviluppatori suggeriscono solo un'enumerazione per tutti gli eventi del grafico: ENUM_CHART_EVENT.
Secondo la documentazione, ci sono 65535 identificatori di eventi personalizzati. Il primo e l'ultimo identificatore degli eventi personalizzati sono impostati dai valori espliciti di CHARTEVENT_CUSTOM e CHARTEVENT_CUSTOM_LAST, che è numericamente uguale a 1000 e 66534 di conseguenza (Fig.1).
Fig.1 Il primo e l'ultimo identificatore di eventi personalizzati
Semplici calcoli considerando il primo e l'ultimo identificatore produrranno: 66534-1000+1=65535.
Prima di utilizzare gli eventi personalizzati, devono essere prima progettati. In questo senso uno sviluppatore diventa una mente e l'autore del concept dell'evento, che viene poi implementato come algoritmo per il futuro Expert. Sarebbe utile avere una classificazione degli eventi personalizzati. Questo metodo conoscitivo non permetterà di eliminare l'ambiguità ma certamente la ridurrà e organizzerà la linea di ragionamento.
Consideriamo un tale criterio di un evento personalizzato come fonte. Ad esempio, lo sviluppatore sergeev ha suggerito l'idea di un prototipo di robot commerciale. Divide tutti gli eventi in tre gruppi (Fig.2).
Fig.2 Gruppi di origini eventi personalizzate
Quindi, secondo questa idea principale, gli eventi personalizzati devono essere sviluppati in base alla loro appartenenza al gruppo.
Cerchiamo di fare qualcosa di semplice per cominciare. Dapprima prenderemo il primo gruppo, che comprende eventi indicatori. Gli eventi che possono appartenere a questo gruppo sono: creazione e cancellazione di un indicatore, ricezione di un segnale di apertura e chiusura di una posizione. Il secondo gruppo include eventi di modifica dello stato di ordini e posizioni. Nel nostro esempio, l'apertura e la chiusura delle posizioni sarà in questo gruppo. È tutto molto semplice. E, infine, il gruppo più complesso per la formalizzazione è un gruppo di eventi esterni.
Prendiamo due eventi: abilitare e disabilitare il trading manuale.
Fig.3 Fonti di eventi personalizzati
Lo schema primario può essere stabilito attraverso il metodo deduttivo (dal generale allo speciale) (Fig.3). Questo è lo schema che utilizzeremo in seguito per creare tipi di eventi nella classe corrispondente (Tabella 1).
Tabella 1 Eventi personalizzati
Questa tabella non può ancora essere definita un "concetto di evento", ma è un inizio. Ecco un altro approccio. È risaputo che un modello di un sistema di trading astratto consiste di tre sottosistemi: moduli di base (Fig.4).
Fig.4 Modello di un sistema di trading astratto
Gli eventi personalizzati basati sul criterio "sorgente" possono essere classificati come eventi generati in:
- il sottosistema del segnale;
- sottosistema delle posizioni aperte finali;
- sottosistema di gestione del denaro.
Quest'ultimo, ad esempio, può includere eventi come il raggiungimento del livello di drawdown consentito, l'aumento del volume degli scambi di un valore prestabilito, l'aumento della percentuale di un limite di perdita, ecc.
2. Gestore e generatore di ChartEvent
Le seguenti poche righe saranno dedicate al gestore e generatore di eventi grafici. Per quanto riguarda la gestione di un evento grafico personalizzato, il suo principio è simile a quello della gestione di un evento grafico tipico.
Un gestore, la funzione OnChartEvent(), accetta quattro costanti come parametri. Apparentemente, gli sviluppatori hanno utilizzato questo meccanismo per implementare l'idea di identificare un evento e ottenere ulteriori informazioni su di esso. A mio parere è un meccanismo di programma molto compatto e conveniente.
La funzione EventChartCustom() genera un evento grafico personalizzato. Sorprendentemente, è possibile creare un evento grafico personalizzato per il grafico "proprio" e per uno "alieno". Penso che l'articolo più interessante sul significato dei grafici propri e alieni sia L'implementazione di una modalità multivaluta in MetaTrader 5.
A mio avviso c'è una discordia nel fatto che l'identificatore dell'evento è di tipo ushort nel generatore, mentre nell'handler è di tipo int. Sarebbe logico usare anche il tipo di dati ushort nel gestore.
3. Classe di evento personalizzato
Come ho detto prima, il concetto dell'evento spetta allo sviluppatore Expert. Ora lavoreremo con gli eventi della Tabella 1. Per prima cosa ordineremo la classe dell'evento personalizzato CEventBase e le sue derivate (Fig.5).
Fig.5 Gerarchia delle classi di eventi
La classe base si presenta come segue:
//+------------------------------------------------------------------+ //| Class CEventBase. | //| Purpose: base class for a custom event | //| Derives from class CObject. | //+------------------------------------------------------------------+ class CEventBase : public CObject { protected: ENUM_EVENT_TYPE m_type; ushort m_id; SEventData m_data; public: void CEventBase(void) { this.m_id=0; this.m_type=EVENT_TYPE_NULL; }; void ~CEventBase(void){}; //-- bool Generate(const ushort _event_id,const SEventData &_data, const bool _is_custom=true); ushort GetId(void) {return this.m_id;}; private: virtual bool Validate(void) {return true;}; };
Il tipo di evento è impostato dall'enumerazione ENUM_EVENT_TYPE:
//+------------------------------------------------------------------+ //| A custom event type enumeration | //+------------------------------------------------------------------+ enum ENUM_EVENT_TYPE { EVENT_TYPE_NULL=0, // no event //--- EVENT_TYPE_INDICATOR=1, // indicator event EVENT_TYPE_ORDER=2, // order event EVENT_TYPE_EXTERNAL=3, // external event };
I membri dei dati comprendono l'identificatore dell'evento e la struttura dei dati.
Il metodo Generate() della classe base CEventBase si occupa della generazione di un evento. Il metodo GetId() restituisce l'id dell'evento e il metodo virtuale Validate() controlla il valore dell'identificatore dell'evento. All'inizio ho incluso il metodo di gestione degli eventi nella classe, ma in seguito mi sono reso conto che ogni evento è unico e un metodo astratto non è sufficiente qui. Ho finito per delegare questa attività alla classe CEventProcessor che gestisce gli eventi personalizzati.
4. Classe gestore eventi personalizzato
La classe CEventProcessor dovrebbe generare e gestire otto eventi presentati. I membri dati della classe hanno il seguente aspetto:
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+----------------------------Data members--------------------------+ protected: ulong m_magic; //--- flags bool m_is_init; bool m_is_trade; //--- CEventBase *m_ptr_event; //--- CTrade m_trade; //--- CiMA m_fast_ema; CiMA m_slow_ema; //--- CButton m_button; bool m_button_state; //+------------------------------------------------------------------+ };
Tra l'elenco degli attributi ci sono i flag di inizializzazione e commercio. Il primo non consentirà all'EA di negoziare se non si avvia correttamente. Il secondo controlla il permesso per il trading.
C'è anche il puntatore all'oggetto del tipo CEventBase, che lavora con eventi di diverso tipo usando il polimorfismo. Un'istanza della classe CTrade fornisce l'accesso alle operazioni di trading.
Gli oggetti di tipo CiMA facilitano la gestione dei dati ricevuti dagli indicatori. Per semplificare l'esempio, ho preso due medie mobili che riceveranno un segnale di trading. Esiste anche un'istanza della classe "CButton" che verrà utilizzata per l'abilitazione/disabilitazione manuale dell'EA.
I metodi della classe sono stati divisi dal principio "moduli – procedure – funzioni – macro":
//+------------------------------------------------------------------+ //| Class CEventProcessor. | //| Purpose: base class for an event processor EA | //+------------------------------------------------------------------+ class CEventProcessor { //+-------------------------------Methods----------------------------+ public: //--- constructor/destructor void CEventProcessor(const ulong _magic); void ~CEventProcessor(void); //--- Modules //--- event generating bool Start(void); void Finish(void); void Main(void); //--- event processing void ProcessEvent(const ushort _event_id,const SEventData &_data); private: //--- Procedures void Close(void); void Open(void); //--- Functions ENUM_ORDER_TYPE CheckCloseSignal(const ENUM_ORDER_TYPE _close_sig); ENUM_ORDER_TYPE CheckOpenSignal(const ENUM_ORDER_TYPE _open_sig); bool GetIndicatorData(double &_fast_vals[],double &_slow_vals[]); //--- Macros void ResetEvent(void); bool ButtonStop(void); bool ButtonResume(void); };
Tra i moduli ce ne sono tre che generano solo eventi: quello iniziale—Start(), quello finale—Finish() e quello principale—Main(). Il quarto modulo ProcessEvent() è sia un gestore di eventi che un generatore.
4.1 Modulo iniziale
Questo modulo è progettato per essere chiamato nel gestore OnInit().
//+------------------------------------------------------------------+ //| Start module | //+------------------------------------------------------------------+ bool CEventProcessor::Start(void) { //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+1 event if(this.m_ptr_event.Generate(1,data)) //--- create a button if(this.m_button.Create(0,"Start_stop_btn",0,25,25,150,50)) if(this.ButtonStop()) { this.m_button_state=false; return true; } } //--- return false; }
In questo modulo viene creato un puntatore all'oggetto evento indicatore. Quindi viene generato l'evento "Creazione indicatore". Un pulsante è l'ultimo ad essere creato. Si passa alla modalità "Stop". Significa che se si preme il pulsante, l'Esperto smette di funzionare.
Anche la struttura EventData è coinvolta in questa definizione del metodo. È un semplice contenitore per i parametri passati al generatore dell'evento personalizzato. Qui verrà compilato solo un campo della struttura: è il campo di tipo lungo. Conterrà il numero magico di EA.
4.2 Modulo di finitura
Questo modulo dovrebbe essere chiamato nel gestore OnDeinit().
//+------------------------------------------------------------------+ //| Finish module | //+------------------------------------------------------------------+ void CEventProcessor::Finish(void) { //--- reset the event object this.ResetEvent(); //--- create an indicator event object this.m_ptr_event=new CIndicatorEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; //--- generate CHARTEVENT_CUSTOM+2 event bool is_generated=this.m_ptr_event.Generate(2,data,false); //--- process CHARTEVENT_CUSTOM+2 event if(is_generated) this.ProcessEvent(CHARTEVENT_CUSTOM+2,data); } }
Qui il puntatore dell'evento precedente viene cancellato e viene generato l'evento "Cancellazione indicatore". Devo notare che se un evento personalizzato viene generato nel gestore OnDeinit(), otterrai un errore di runtime 4001 (errore esterno imprevisto). Pertanto, la generazione e la gestione degli eventi vengono eseguite all'interno di questo metodo senza chiamare OnChartEvent().
Anche in questo caso, il numero magico di EA verrà archiviato utilizzando la struttura SEventData.
4.3 Modulo principale
Questo modulo dovrebbe essere chiamato nel gestore OnTick().
//+------------------------------------------------------------------+ //| Main module | //+------------------------------------------------------------------+ void CEventProcessor::Main(void) { //--- a new bar object static CisNewBar newBar; //--- if initialized if(this.m_is_init) //--- if not paused if(this.m_is_trade) //--- if a new bar if(newBar.isNewBar()) { //--- close module this.Close(); //--- open module this.Open(); } }
In questo modulo vengono chiamate le procedure Open() e Close(). La prima procedura può generare l'evento "Ricezione segnale di apertura" e la seconda - "Ricezione segnale di chiusura". L'attuale versione del modulo è completamente funzionante con un nuovo aspetto della barra. Una classe per rilevare un La nuova barra è stata descritta da Konstantin Gruzdev.
4.4 Modulo di gestione degli eventi
Questo modulo dovrebbe essere chiamato nel gestore OnChartEvent(). Questo modulo è il più grande in termini di dimensioni e funzionalità.
//+------------------------------------------------------------------+ //| Process event module | //+------------------------------------------------------------------+ void CEventProcessor::ProcessEvent(const ushort _event_id,const SEventData &_data) { //--- check event id if(_event_id==CHARTEVENT_OBJECT_CLICK) { //--- button click if(StringCompare(_data.sparam,this.m_button.Name())==0) { //--- button state bool button_curr_state=this.m_button.Pressed(); //--- to stop if(button_curr_state && !this.m_button_state) { if(this.ButtonResume()) { this.m_button_state=true; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+7 event ushort curr_id=7; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- to resume else if(!button_curr_state && this.m_button_state) { if(this.ButtonStop()) { this.m_button_state=false; //--- reset the event object this.ResetEvent(); //--- create an external event object this.m_ptr_event=new CExternalEvent(); //--- if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)TimeCurrent(); //--- generate CHARTEVENT_CUSTOM+8 event ushort curr_id=8; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } } } //--- user event else if(_event_id>CHARTEVENT_CUSTOM) { long magic=_data.lparam; ushort curr_event_id=this.m_ptr_event.GetId(); //--- check magic if(magic==this.m_magic) //--- check id if(curr_event_id==_event_id) { //--- process the definite user event switch(_event_id) { //--- 1) indicator creation case CHARTEVENT_CUSTOM+1: { //--- create a fast ema if(this.m_fast_ema.Create(_Symbol,_Period,21,0,MODE_EMA,PRICE_CLOSE)) if(this.m_slow_ema.Create(_Symbol,_Period,55,0,MODE_EMA,PRICE_CLOSE)) if(this.m_fast_ema.Handle()!=INVALID_HANDLE) if(this.m_slow_ema.Handle()!=INVALID_HANDLE) { this.m_trade.SetExpertMagicNumber(this.m_magic); this.m_trade.SetDeviationInPoints(InpSlippage); //--- this.m_is_init=true; } //--- break; } //--- 2) indicator deletion case CHARTEVENT_CUSTOM+2: { //---release indicators bool is_slow_released=IndicatorRelease(this.m_fast_ema.Handle()); bool is_fast_released=IndicatorRelease(this.m_slow_ema.Handle()); if(!(is_slow_released && is_fast_released)) { //--- to log? if(InpIsLogging) Print("Failed to release the indicators!"); } //--- reset the event object this.ResetEvent(); //--- break; } //--- 3) check open signal case CHARTEVENT_CUSTOM+3: { MqlTick last_tick; if(SymbolInfoTick(_Symbol,last_tick)) { //--- signal type ENUM_ORDER_TYPE open_ord_type=(ENUM_ORDER_TYPE)_data.dparam; //--- double open_pr,sl_pr,tp_pr,coeff; open_pr=sl_pr=tp_pr=coeff=0.; //--- if(open_ord_type==ORDER_TYPE_BUY) { open_pr=last_tick.ask; coeff=1.; } else if(open_ord_type==ORDER_TYPE_SELL) { open_pr=last_tick.bid; coeff=-1.; } sl_pr=open_pr-coeff*InpStopLoss*_Point; tp_pr=open_pr+coeff*InpStopLoss*_Point; //--- to normalize prices open_pr=NormalizeDouble(open_pr,_Digits); sl_pr=NormalizeDouble(sl_pr,_Digits); tp_pr=NormalizeDouble(tp_pr,_Digits); //--- open the position if(!this.m_trade.PositionOpen(_Symbol,open_ord_type,InpTradeLot,open_pr, sl_pr,tp_pr)) { //--- to log? if(InpIsLogging) Print("Failed to open the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+5 event ushort curr_id=5; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } } //--- break; } //--- 4) check close signal case CHARTEVENT_CUSTOM+4: { if(!this.m_trade.PositionClose(_Symbol)) { //--- to log? if(InpIsLogging) Print("Failed to close the position: "+_Symbol); } else { //--- pause Sleep(InpTradePause); //--- reset the event object this.ResetEvent(); //--- create an order event object this.m_ptr_event=new COrderEvent(); if(CheckPointer(this.m_ptr_event)==POINTER_DYNAMIC) { SEventData data; data.lparam=(long)this.m_magic; data.dparam=(double)this.m_trade.ResultDeal(); //--- generate CHARTEVENT_CUSTOM+6 event ushort curr_id=6; if(!this.m_ptr_event.Generate(curr_id,data)) PrintFormat("Failed to generate an event: %d",curr_id); } } //--- break; } //--- 5) position opening case CHARTEVENT_CUSTOM+5: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_IN) { //--- to log? if(InpIsLogging) { Print("\nNew position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 6) position closing case CHARTEVENT_CUSTOM+6: { ulong ticket=(ulong)_data.dparam; ulong deal=(ulong)_data.dparam; //--- datetime now=TimeCurrent(); //--- check the deals & orders history if(HistorySelect(now-PeriodSeconds(PERIOD_H1),now)) if(HistoryDealSelect(deal)) { double deal_vol=HistoryDealGetDouble(deal,DEAL_VOLUME); ENUM_DEAL_ENTRY deal_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY); //--- if(deal_entry==DEAL_ENTRY_OUT) { //--- to log? if(InpIsLogging) { Print("\nClosed position for: "+_Symbol); PrintFormat("Volume: %0.2f",deal_vol); } } } //--- break; } //--- 7) stop trading case CHARTEVENT_CUSTOM+7: { datetime stop_time=(datetime)_data.dparam; //--- this.m_is_trade=false; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is stopped at: %s", TimeToString(stop_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } //--- 8) resume trading case CHARTEVENT_CUSTOM+8: { datetime resume_time=(datetime)_data.dparam; this.m_is_trade=true; //--- to log? if(InpIsLogging) PrintFormat("Expert trading is resumed at: %s", TimeToString(resume_time,TIME_DATE|TIME_MINUTES|TIME_SECONDS)); //--- break; } } } } }
Si compone di due parti. Il primo è la gestione degli eventi collegati con un clic sull'oggetto "Pulsante". Questo clic genererà un evento personalizzato esterno, che verrà gestito dal gestore in un secondo momento.
La seconda parte è progettata per l'elaborazione degli eventi personalizzati generati. Contiene due blocchi, dove dopo che un evento rilevante è stato gestito, ne viene generato uno nuovo. Nel primo blocco viene elaborato l'evento "Ricezione segnale di apertura". La sua corretta gestione genera un nuovo evento di ordine "Apertura di una posizione". Nel secondo blocco viene elaborato l'evento "Ricezione segnale di chiusura". Se il segnale viene gestito, si verifica l'evento "Chiusura di una posizione".
L'Expert CustomEventProcessor.mq5 è un buon esempio dell'utilizzo della classe CEventProcessor. L'EA è stato progettato per creare eventi e rispondere in modo appropriato. Con il paradigma OPP, siamo stati in grado di ridurre al minimo il codice sorgente a un numero inferiore di righe. Il codice sorgente di EA può essere trovato in allegato a questo articolo.
A mio avviso, non è necessario fare riferimento ogni volta al meccanismo di un evento personalizzato. Ci sono molte cose minori, insignificanti e senza eventi in termini di strategia che possono avere una forma diversa.
Conclusione
In questo articolo ho cercato di illustrare i principi per lavorare con eventi personalizzati nell'ambiente MQL5. Spero che le idee trattate in questo articolo siano di interesse per i programmatori con esperienze diverse, non solo per i principianti.
Sono contento che il linguaggio MQL5 si stia sviluppando. Probabilmente, nel prossimo futuro ci saranno modelli di classe e potrebbero essere puntatori a funzioni. Quindi saremo in grado di scrivere un delegato a tutti gli effetti che punta a un metodo di un oggetto arbitrario.
I file sorgente dall'archivio possono essere inseriti in una cartella del progetto. Nel mio caso, è la cartella MQL5\Projects\ChartUserEvent.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/1163





- 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