
Il Prototipo di Robot di Trading
Introduzione
Il ciclo di vita di qualsiasi sistema di trading è ridotto all'apertura e alla chiusura di posizioni. Questo è fuori da ogni dubbio. Ma quando si tratta della realizzazione dell'algoritmo, qui ci sono tante opinioni quanti sono i programmatori. Ognuno sarà in grado di risolvere lo stesso problema a modo suo, ma con lo stesso risultato finale.
Nel corso degli anni, sono stati provati diversi approcci alla costruzione della logica e della struttura degli expert. Al momento, si può sostenere che ha stabilito un modello di template chiaro che viene utilizzato in tutti i codici.
Questo approccio non è universale al 100%, ma può cambiare il tuo metodo di progettazione della logica dell'expert. E il caso non è quali capacità di lavorare con gli ordini vuoi utilizzare con l'expert. L'intero punto - è il principio della creazione di un modello di trading.
1. Principi di Progettazione di Sistemi di Trading e Tipi di Fonti di Eventi
L'approccio di base alla progettazione dell'algoritmo, utilizzato dalla maggioranza, è quello di tracciare una posizione dalla sua apertura fino alla chiusura. Questo è un approccio lineare. Se vuoi apportare modifiche al codice, spesso porta a grandi complicazioni, poiché emerge un gran numero di condizioni e il codice accumula nuovi rami di analisi.
La soluzione migliore per creare un robot di trading è "servire le condizioni". E principio fondamentale - analizzare non come è nata questa condizione di sistema esperto e le sue posizioni e ordini - ma cosa dovremmo fare con loro ora. Questo principio di base sta cambiando radicalmente la gestione del trading e semplifica lo sviluppo del codice.
Analizzalo in modo più dettagliato.
1.1. Il Principio di "Servire le Condizioni"
Come già accennato, l'expert non ha bisogno di sapere come è stato raggiunto lo stato attuale. Deve sapere cosa fare con esso ora in base al suo ambiente (valori dei parametri, proprietà degli ordini memorizzati, ecc.).
Questo principio è direttamente correlato al fatto che l'expert esiste da loop a loop (in particolare - dal tick al tick), e non dovrebbe preoccuparsi di ciò che è successo con gli ordini al tick precedente. Pertanto, è necessario utilizzare un approccio basato sugli eventi di gestione degli ordini. In altre parole, sul tick corrente l'expert salva il suo stato che è il punto di partenza per la decisione sul prossimo tick.
Ad esempio, è necessario rimuovere tutti gli ordini in sospeso di expert e solo allora continuare ad analizzare gli indicatori e ad effettuare nuovi ordini. La maggior parte degli esempi di codice che abbiamo visto usano il loop "while (true) {try to remove}" o il loop leggermente più morbido "while (k < 1000) {try to remove; k++;}" Salteremo la variante in cui una chiamata una tantum del comando di rimozione senza analisi degli errori.
Questo metodo è lineare, "blocca" l'expert per un periodo di tempo indefinito.
Pertanto, sarà più corretto non eseguire il loop di un expert, ma archiviare l'ordine per rimuovere gli ordini, in modo che ad ogni nuovo tick questo ordine venga controllato mentre si tenta di eliminare l'ordine in sospeso. In questo caso, un expert, mentre legge i parametri di stato, sa che in questo momento deve eliminare gli ordini e tenterà di rimuoverli. Se si verificherà un errore di trading, un expert bloccherà semplicemente ulteriori analisi e lavorerà prima del ciclo successivo.
1.2. Il Secondo Principio Fondamentale della Progettazione - è la massima astrazione possibile dalla direzione della posizione considerata (Buy/Sell), valuta e grafico. Tutte le funzioni expert dovrebbero essere implementate in modo tale che la direzione o il simbolo vengano analizzati in rari casi in cui non può davvero essere evitato (ad esempio, se si considera la crescita favorevole del prezzo per la posizione aperta, sebbene ci siano diverse opzioni per evitare le specifiche). Cerca sempre di evitare un progettazione così "di basso livello". Questo ridurrà il codice e il processo di scrittura delle funzioni di almeno due volte e li renderà “indipendenti dal trading”.
L'implementazione di questo principio di sostituire l'analisi esplicita dei tipi di ordine, dei parametri dei simboli e dei parametri calcolati dipendenti con le macro-funzioni. Nel prossimo articolo tratteremo più nel dettaglio questa implementazione.
1.3. Terzo principio –segmentazione dell'algoritmo in lessemi logici (moduli indipendenti)
In pratica, possiamo dire che l'approccio migliore è la separazione delle operazioni degli expert in singole funzioni. Penso che sarai d'accordo sul fatto che è difficile scrivere l'intero algoritmo dell'expert che scrive in una funzione e complica la successiva analisi e modifica. Quindi, non dovremmo farlo in MQL5 che ora fornisce un controllo quasi completo sul tuo ambiente.
Pertanto, i lessemi logici (ad es. apertura, trailing, chiusura degli ordini) dovrebbero essere implementati separatamente l'uno dall'altro con un'analisi completa dei parametri e degli eventi dell’ambiente. Attraverso questo approccio, l'expert diventa flessibile nella progettazione. È possibile aggiungere facilmente nuovi moduli indipendenti senza toccare quelli esistenti o disabilitare i moduli esistenti senza alterare il codice principale.
Le fonti di eventi per il sistema di expert sono:
1. Indicatori. Un esempio, è l'analisi dei valori delle linee degli indicatori, le loro intersezioni, combinazioni, ecc. Inoltre, gli indicatori possono essere: l'ora corrente, i dati ottenuti da Internet, ecc. Nella maggior parte dei casi, gli eventi degli indicatori vengono utilizzati per segnalare l'apertura e la chiusura degli ordini. Meno per la loro regolazione (di solito Trailing Stop Loss o ordine in sospeso per l'indicatore).
Ad esempio, l'implementazione pratica dell'indicatore può essere definita un expert che analizza l'intersezione della MA veloce e lenta con l'ulteriore apertura della posizione in direzione dell'intersezione.
2. Ordini esistenti, posizioni e loro stato. Ad esempio, la perdita corrente o la dimensione del profitto, la presenza/assenza di posizioni o ordini in sospeso, il profitto della posizione chiusa, ecc. L'attuazione pratica di questi eventi è molto più ampia e diversificata, in quanto ci sono più opzioni della loro relazione rispetto agli eventi indicatori.
L'esempio più semplice di un expert, basato solo sull'evento di trading, è la ricarica per la media della posizione esistente e la produzione nel profitto desiderato. Vale a dire che la presenza di perdita sulla posizione disponibile sarà un evento per effettuare un nuovo ordine di media.
Oppure, ad esempio, Trailing Stop Loss. Questa funzione controlla un evento quando il prezzo si sposta nel profitto per un numero specificato di punti dal precedente Stop Loss. Di conseguenza, l'expert tira il tuo Stop Loss dopo il prezzo.
3. Eventi esterni. Sebbene tale evento di solito non si verifichi in un sistema puramente esperto, in generale dovrebbe essere considerato per prendere una decisione. Ciò include la regolazione degli ordini, delle posizioni, l'elaborazione degli errori di trading, l'elaborazione degli eventi del grafico (spostamento/creazione/eliminazione di oggetti, premere pulsanti, ecc.). In generale, questi sono gli eventi che non sono verificabili sulla storia e si verificano solo quando lavorano gli expert.
Un esempio lampante di tali expert sono i sistemi di informazione trading con controllo grafico del trading.
2. La classe base CExpertAdvisor – expert constructor
Quale sarà il lavoro di trade expert? Lo schema generale delle interazioni MQL-programma è mostrato nel diagramma seguente.
Figura 1. Schema generale delle interazioni tra gli elementi del programma MQL
Come puoi vedere dallo schema, prima arriva l'ingresso al circuito di lavoro (questo può essere un tick o un segnale timer). In questa fase del primo blocco, questo tick può essere filtrato senza elaborazione. Ciò avviene nei casi in cui l'expert non è necessario per lavorare su ogni tick, ma solo su una nuova barra o se semplicemente l'expert non è autorizzato a lavorare.
Quindi, il programma va in secondo blocco - i moduli di lavoro con ordini e posizioni e solo allora i blocchi di elaborazione degli eventi vengono chiamati dai moduli. Ogni modulo può interrogare solo sul suo evento interessato.
Questa sequenza può essere chiamata come schema con logica diretta, poiché prima determina COSA farà l'expert (quali moduli di elaborazione degli eventi vengono utilizzati), e solo allora implementa COME e PERCHÉ lo farà (ottenendo segnali di evento).
La logica diretta è coerente con la nostra percezione del mondo e con la logica universale. Dopotutto, un uomo pensa prima concetti concreti, poi li riassume e poi classifica e identifica le loro interconnessioni.
Gli esperti di progettazione non fanno eccezione in questo senso. In primo luogo, viene dichiarato cosa dovrebbe fare un expert (aprire e chiudere le posizioni, tirare l'arresto protettivo), e solo allora, viene specificato in quali eventi e come dovrebbe farlo. Ma in ogni caso non viceversa: ricevi il segnale e pensa dove e come elaborarlo. Questa è la logica inversa ed è meglio non usarla poiché, di conseguenza, otterrai un codice ingombrante con un gran numero di rami di condizione.
Ecco un esempio di logica inversa e diretta. Prendi l'apertura/chiusura dal segnale RSI.
- Nella logica inversa l'expert inizia ad l'ottenere il valore dell'indicatore e poi controlla la direzione del segnale e cosa devi fare con la posizione: aprire il Buy e chiudere il Sell, o viceversa, aprire il Sell e chiudere il Buy. In altre parole, il punto di ingresso è quello per ottenere e analizzare il segnale.
- Nella logicaI diretta è tutto al contrario. L’expert ha due moduli di posizioni di apertura e chiusura e controlla semplicemente le condizioni per eseguire questi moduli. Vale a dire, dopo aver inserito il modulo di apertura, l'expert riceve il valore dell'indicatore e controlla se si tratta di un segnale da aprire. Quindi, dopo aver inserito il modulo di chiusura degli ordini, l'expert verifica se si tratta di un segnale per chiudere la posizione. Cioè, non c'è un punto di ingresso - ci sono moduli di analisi dello stato del sistema che funzionano in modo indipendente (il primo principio di progettazione).
Ora, se vuoi complicare l'expert, sarà molto più facile usare la seconda variante rispetto alla prima. Sarà sufficiente creare un nuovo modulo di elaborazione degli eventi.
E nella prima variante dovrai rivedere la struttura dell'elaborazione del segnale o incollarla come funzione separata.
Per comprendere meglio questo approccio, ecco i diversi schemi di lavoro nei contesti di quattro diversi expert.
Figura 2. Esempi di implementazione degli expert
a). Expert, basato solo sui segnali di qualche indicatore. Può aprire e chiudere posizioni quando il segnale sta cambiando. Esempio: un expert MA.
b). Esperto con controllo grafico del trading.
c). Expert basato sugli indicatori, ma con aggiunta di Trailing Stop Loss e tempo operativo. Esempio: scalping sulle notizie con posizione di apertura nella tendenza dall'indicatore MA.
d). Expert senza indicatori, con medie delle posizioni. Verifica i parametri di posizione una sola volta quando si apre una nuova barra. Esempio - expert delle medie
3. Implementazione della Classe Expert
Crea una classe utilizzando tutte le regole e i requisiti sopra menzionati che saranno la base per tutti i futuri expert.
La funzionalità minima che dovrebbe essere nella classe CExpertAdvisor è la seguente:
1. Inizializzazione:
- Indicatori di Registro
- Impostare i valori iniziali dei parametri
- Regola il simbolo e l'intervallo richiesti
2. Funzioni per Ottenere Segnali
- Orario di lavoro consentito (intervalli scambiati)
- Determinare il segnale per aprire/chiudere posizioni o ordini
- Determinare il filtro (tendenza, ora, ecc.)
- Timer di avvio/arresto
3. Funzioni di Servizio
- Calcola il prezzo di apertura, i livelli SL e TP, il volume dell'ordine
- Invia richieste di trading (apri, chiudi, modifica)
4. Moduli di Trading
- Elaborare i segnali e i filtri
- Controlla le posizioni e gli ordini
- Lavora nelle funzioni degli expert: OnTrade(), OnTimer(), OnTester(), OnChartEvent().
5. Reinizializzazione
- Messaggi di output, report
- Grafico chiaro, indicatori di unload
Tutte le funzioni della classe sono divise in tre gruppi. Lo schema generale delle funzioni nidificate e le loro descrizioni vengono illustrati di seguito.
Figura 3. Schema delle funzioni nidificate di un expert
1. Macro Funzioni
Questo piccolo gruppo di funzioni è la base per lavorare con i tipi di ordine, i parametri dei simboli e i valori di prezzo per impostare gli ordini (l'apertura e gli arresti). Queste macro funzioni forniscono il secondo principio di progettazione: l'astrazione. Funzionano nel contesto del simbolo che viene utilizzato dall'expert.
Le macro funzioni dei tipi di conversione funzionano con la direzione del mercato: acquista o vendi. Pertanto, per non creare le tue costanti, usa meglio quelle esistenti - ORDER_TYPE_BUY e ORDER_TYPE_SELL. Ecco alcuni esempi di utilizzo di macro e i risultati del loro lavoro.
//--- Type conversion macro long BaseType(long dir); // returns the base type of order for specified direction long ReversType(long dir); // returns the reverse type of order for specified direction long StopType(long dir); // returns the stop-order type for specified direction long LimitType(long dir); // returns the limit-order type for specified direction //--- Normalization macro double BasePrice(long dir); // returns Bid/Ask price for specified direction double ReversPrice(long dir); // returns Bid/Ask price for reverse direction long dir,newdir; dir=ORDER_TYPE_BUY; newdir=ReversType(dir); // newdir=ORDER_TYPE_SELL newdir=StopType(dir); // newdir=ORDER_TYPE_BUY_STOP newdir=LimitType(dir); // newdir=ORDER_TYPE_BUY_LIMIT newdir=BaseType(newdir); // newdir=ORDER_TYPE_BUY double price; price=BasePrice(dir); // price=Ask price=ReversPrice(dir); // price=Bid
Durante lo sviluppo di expert, la macro consente di non specificare la direzione elaborata e aiuta a creare un codice più compatto.
2. Funzioni di Servizio
Queste funzioni sono progettate per operare con ordini e posizioni. Come la marco funzione, anche essi sono di basso livello. Per comodità, possono essere suddivisi in due categorie: funzioni informative e funzioni esecutive. Tutte eseguono un solo tipo di azione, senza analizzare alcun evento. Eseguono ordini da handler di expert senior.
Esempi di funzioni informative: trovare il prezzo massimo di apertura degli ordini in sospeso correnti; scoprire come è stata chiusa la posizione - con profitti o perdite; ottenere il numero e l'elenco dei ticket degli ordini degli expert, ecc.
Esempi di funzioni esecutive: chiusura degli ordini specificati; modifica dello Stop Loss nella posizione specificata, ecc.
Questo gruppo è il più grande. Questo è il tipo di funzionalità su cui si basa l'intero lavoro di routine dell'expert. Sul forum al link https://www.mql5.com/ru/forum/107476, si trova un gran numero di esempi di queste funzioni. Ma oltre a questo, la libreria standard MQL5 contiene già classi che assumono su di sé parte del lavoro sull'inserimento di ordini e posizioni, in particolare - la classe CTrade.
Ma qualsiasi tua attività richiederà di creare nuove implementazioni o di modificare leggermente quelle esistenti.
3. Moduli di Elaborazione degli Eventi
Il gruppo di queste funzioni è una sovrastruttura di alto livello rispetto ai primi due gruppi. Come accennato in precedenza, si tratta di blocchi pronti all'uso di cui è costituito il tuo expert. In generale, sono inclusi nella funzione di elaborazione degli eventi del programma MQL: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent(). Questo gruppo non è numeroso e il contenuto di questi moduli può essere regolato da un'attività all'altra. Tuttavia, in sostanza non cambia nulla.
Nei moduli, tutto dovrebbe essere astratto (il secondo principio di progettazione) in modo che lo stesso modulo possa essere invocato sia per l'acquisto che per la vendita. Ciò si ottiene, ovviamente, con l'aiuto della macro.
Quindi, procedi con l'implementazione
1. Inizializzazione, Reinizializzazione
class CExpertAdvisor { protected: bool m_bInit; // flag of correct initialization ulong m_magic; // magic number of expert string m_smb; // symbol, on which expert works ENUM_TIMEFRAMES m_tf; // working timeframe CSymbolInfo m_smbinf; // symbol parameters int m_timer; // time for timer public: double m_pnt; // consider 5/3 digit quotes for stops CTrade m_trade; // object to execute trade orders string m_inf; // comment string for information about expert's work
Questo è il set minimo richiesto di parametri per il funzionamento delle funzioni dell’expert.
I parametri m_smb e m_tf sono specificamente posizionati nelle proprietà degli expert per dire facilmente all'expert su quale valuta e in quale periodo lavorare. Ad esempio, se si assegna m_smb = "USDJPY", l'expert lavorerà su quel simbolo, indipendentemente dal simbolo su cui è stato eseguito. Se imposti tf = PERIOD_H1, tutti i segnali e l'analisi degli indicatori avranno luogo sul grafico H1.
Inoltre, ci sono i metodi di classe. I primi tre metodi sono l'inizializzazione e la reinizializzazione di un expert.
public: //--- Initialization void CExpertAdvisor(); // constructor void ~CExpertAdvisor(); // destructor virtual bool Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization
Il constructor e il destructor nella classe base non fanno nulla.
Il metodo Init() consente l'inizializzazione dei parametri expert da parte del simbolo, dell'intervallo e del numero magico.
//------------------------------------------------------------------ CExpertAdvisor void CExpertAdvisor::CExpertAdvisor() { m_bInit=false; } //------------------------------------------------------------------ ~CExpertAdvisor void CExpertAdvisor::~CExpertAdvisor() { } //------------------------------------------------------------------ Init bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf) { m_magic=magic; m_smb=smb; m_tf=tf; // set initializing parameters m_smbinf.Name(m_smb); // initialize symbol m_pnt=m_smbinf.Point(); // calculate multiplier for 5/3 digit quote if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10; m_trade.SetExpertMagicNumber(m_magic); // set magic number for expert m_bInit=true; return(true); // trade allowed }
2. Funzioni per Ottenere Segnali
Queste funzioni analizzano il mercato e gli indicatori.
bool CheckNewBar(); // check for new bar bool CheckTime(datetime start,datetime end); // check allowed trade time virtual long CheckSignal(bool bEntry); // check signal virtual bool CheckFilter(long dir); // check filter for direction
Le prime due funzioni hanno un'implementazione abbastanza specifica e possono essere utilizzate su altri figli di questa classe.
//------------------------------------------------------------------ CheckNewBar bool CExpertAdvisor::CheckNewBar() // function of checking new bar { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) // copy bar { Print("CopyRates of ",m_smb," failed, no history"); return(false); } if(rt[1].tick_volume>1) return(false); // check volume return(true); } //--------------------------------------------------------------- CheckTime bool CExpertAdvisor::CheckTime(datetime start,datetime end) { datetime dt=TimeCurrent(); // current time if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range if(start>=end) if(dt>=start|| dt<end) return(true); return(false); }
Gli ultimi due dipendono sempre dagli indicatori che stai utilizzando. È praticamente impossibile impostare queste funzioni per tutti i casi.
La cosa principale: è importante capire che le funzioni di segnale CheckSignal() e CheckFilter() possono analizzare qualsiasi indicatore e le loro combinazioni! Vale a dire che i moduli di trading in cui questi segnali saranno successivamente inclusi sono indipendenti dalle fonti.
Ciò consente di utilizzare un expert scritto una volta come modello per altri expert che lavorano su un principio simile. Basta cambiare gli indicatori analizzati o aggiungere nuove condizioni di filtratura.
3. Funzioni di Servizio
Come già accennato, questo gruppo di funzioni è il più numeroso. Per i nostri compiti pratici descritti nell'articolo, sarà sufficiente implementare quattro di queste funzioni:
double CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk ulong DealOpen(long dir,double lot,int SL,int TP); // execute deal with specified parameter ulong GetDealByOrder(ulong order); // get deal ticket by order ticket double CountProfitByDeal(ulong ticket); // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk { if(dist==0 || risk==0) return(lot); m_smbinf.Refresh(); return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue()))); } //------------------------------------------------------------------ DealOpen ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP) { double op,sl,tp,apr,StopLvl; // determine price parameters m_smbinf.RefreshRates(); m_smbinf.Refresh(); StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level apr = ReversPrice(dir); op = BasePrice(dir); // open price sl = NormalSL(dir, op, apr, SL, StopLvl); // stop loss tp = NormalTP(dir, op, apr, TP, StopLvl); // take profit // open position m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp); ulong order = m_trade.ResultOrder(); if(order<=0) return(0); // order ticket return(GetDealByOrder(order)); // return deal ticket } //------------------------------------------------------------------ GetDealByOrder ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket { PositionSelect(m_smb); HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER)); uint total=HistoryDealsTotal(); for(uint i=0; i<total; i++) { ulong deal=HistoryDealGetTicket(i); if(order==HistoryDealGetInteger(deal,DEAL_ORDER)) return(deal); // remember deal ticket } return(0); } //------------------------------------------------------------------ CountProfit double CExpertAdvisor::CountProfitByDeal(ulong ticket) // position profit by deal ticket { CDealInfo deal; deal.Ticket(ticket); // deal ticket HistorySelect(deal.Time(),TimeCurrent()); // select all deals after this uint total = HistoryDealsTotal(); long pos_id = deal.PositionId(); // get position id double prof = 0; for(uint i=0; i<total; i++) // find all deals with this id { ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue; prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit } return(prof); // return profit }
4. Moduli di Trading
Infine, questo gruppo di funzioni collega l'intero processo di trading, elaborando i segnali e gli eventi, utilizzando le funzioni di servizio e macro. I lessemi logici delle operazioni di trading sono poche, dipendono dai tuoi obiettivi specifici. Tuttavia, possiamo distinguere i concetti comuni che esistono quasi in tutti gli expert.
virtual bool Main(); // main module controlling trade process virtual void OpenPosition(long dir); // module of opening position virtual void CheckPosition(long dir); // check position and open additional ones virtual void ClosePosition(long dir); // close position virtual void BEPosition(long dir,int BE); // moving Stop Loss to break-even virtual void TrailingPosition(long dir,int TS); // trailing position of Stop Loss virtual void OpenPending(long dir); // module of opening pending orders virtual void CheckPending(long dir); // work with current orders and open additional ones virtual void TrailingPending(long dir); // move pending orders virtual void DeletePending(long dir); // delete pending orders
Prenderemo in considerazione implementazioni specifiche di queste funzioni negli esempi seguenti.
Aggiungere nuove funzioni non sarà difficile, dal momento che abbiamo scelto l'approccio giusto e costituito la struttura dell’expert. Se utilizzerai esattamente questo schema, i tuoi progetti richiederanno sforzi e tempo minimi, il codice sarà leggibile anche dopo un anno.
Naturalmente, i tuoi expert non si limitano solo a questi. Nella classe CExpertAdvisor, abbiamo dichiarato solo i metodi più necessari. È possibile aggiungere nuovi handler nelle classi di figli, modificare quelli esistenti, espandere i propri moduli, creando così un'unica libreria. Avendo una libreria simile, lo sviluppo di expert "chiavi in mano" richiede da mezz'ora a due giorni.
4. Esempi di Utilizzo della Classe CExpertAdvisor
4.1. Esempio di Lavoro Basato sui Segnali degli IndicatoriCome primo esempio, iniziamo con l'attività più semplice: consideriamo MovingAverage Expert Advisor (esempio di base di MetaTrader 5) utilizzando la classe CExpertAdvisor. Complichiamolo un po '.
Algoritmo:
a) Condizioni per l'apertura della posizione
- Se il prezzo attraversa la MA bottom-up, allora aprire la posizione per Acquistare.
- Se il prezzo attraversa la MA top-down, aprire la posizione per Vendere.
- Impostare SL (Stop Loss), TP (TakeProfit).
- Il lotto di posizione viene calcolato dal parametro di Rischio: quanti perderanno dal deposito quando viene attivato lo Stop Loss.
b) Condizioni di chiusura della posizione
- Se il prezzo attraversa la MA bottom-up, allora chiudi la posizione per Vendere.
- Se il prezzo attraversa la MA top-down, alla chiudi la posizione per Acquistare.
c) Limitazione
- Limitare il lavoro di un expert nel tempo da HourStart fino a HourEnd ogni giorno.
- Expert effettua operazioni di trading solo su una nuova barra.
d) Supporto alla posizione
- Usa un semplice trailing stop a una distanza di TS.
Per il nostro expert avremo bisogno di sette funzioni della classe CExpertAdvisor:
- Funzione del segnale - CheckSignal()
- Filtro tick - CheckNewBar()
- Filtro orario - CheckTime()
- Funzione di servizio delle posizioni di apertura - DealOpen()
- Tre moduli di lavoro: OpenPosition(), ClosePosition(), TrailingPosition()
La funzione CheckSignal() e i moduli devono essere definiti in una classe figlio per risolvere specificamente il suo compito. Dobbiamo anche aggiungere l'inizializzazione dell'indicatore.
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" input double Risk = 0.1; // Risk input int SL = 100; // Stop Loss distance input int TP = 100; // Take Profit distance input int TS = 30; // Trailing Stop distance input int pMA = 12; // Moving Average period input int HourStart = 7; // Hour of trade start input int HourEnd = 20; // Hour of trade end //--- class CMyEA : public CExpertAdvisor { protected: double m_risk; // size of risk int m_sl; // Stop Loss int m_tp; // Take Profit int m_ts; // Trailing Stop int m_pMA; // MA period int m_hourStart; // Hour of trade start int m_hourEnd; // Hour of trade end int m_hma; // MA indicator public: void CMyEA(); void ~CMyEA(); virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // initialization virtual bool Main(); // main function virtual void OpenPosition(long dir); // open position on signal virtual void ClosePosition(long dir); // close position on signal virtual long CheckSignal(bool bEntry); // check signal }; //------------------------------------------------------------------ CMyEA void CMyEA::CMyEA() { } //----------------------------------------------------------------- ~CMyEA void CMyEA::~CMyEA() { IndicatorRelease(m_hma); // delete MA indicator } //------------------------------------------------------------------ Init bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA; // copy parameters m_hourStart=HourStart; m_hourEnd=HourEnd; m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator if(m_hma==INVALID_HANDLE) return(false); // if there is an error, then exit m_bInit=true; return(true); // trade allowed } //------------------------------------------------------------------ Main bool CMyEA::Main() // main function { if(!CExpertAdvisor::Main()) return(false); // call function of parent class if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars if(!CheckNewBar()) return(true); // check new bar // check each direction long dir; dir=ORDER_TYPE_BUY; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); dir=ORDER_TYPE_SELL; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); return(true); } //------------------------------------------------------------------ OpenPos void CMyEA::OpenPosition(long dir) { if(PositionSelect(m_smb)) return; // if there is an order, then exit if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) return; if(dir!=CheckSignal(true)) return; // if there is no signal for current direction double lot=CountLotByRisk(m_sl,m_risk,0); if(lot<=0) return; // if lot is not defined then exit DealOpen(dir,lot,m_sl,m_tp); // open position } //------------------------------------------------------------------ ClosePos void CMyEA::ClosePosition(long dir) { if(!PositionSelect(m_smb)) return; // if there is no position, then exit if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) { m_trade.PositionClose(m_smb); return; } // if it's not time for trade, then close orders if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction if(dir!=CheckSignal(false)) return; // if the close signal didn't match the current position m_trade.PositionClose(m_smb,1); // close position } //------------------------------------------------------------------ CheckSignal long CMyEA::CheckSignal(bool bEntry) { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); } double ma[1]; if(CopyBuffer(m_hma,0,0,1,ma)!=1) { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); } if(rt[0].open<ma[0] && rt[0].close>ma[0]) return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy if(rt[0].open>ma[0] && rt[0].close<ma[0]) return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell return(WRONG_VALUE); // if there is no signal } CMyEA ea; // class instance //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // initialize expert return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.Main(); // process incoming tick }
Analizziamo la struttura della funzione Main(). Convenzionalmente, essa è divisa in due parti.
Nella prima parte, viene chiamata la funzione padre. Questa funzione elabora i possibili parametri che influenzano complessivamente il lavoro di un expert. Questi includono il controllo dell'indennità di trading per un expert e la convalida dei dati storici.
Nella seconda parte, gli eventi di mercato vengono elaborati direttamente.
Viene testato il filtro CheckNewBar() controllando una nuova barra. I moduli per due direzioni di trading vengono chiamati uno dopo l'altro.
Nei moduli, tutto è organizzato in modo piuttosto astratto (il secondo principio di progettazione). Non esiste un indirizzo diretto per le proprietà del simbolo. I tre moduli - OpenPosition(),ClosePosition() e TrailingPosition() - si basano solo su quei parametri che arrivano loro dall'esterno. Ciò permette di chiamare questi moduli per la verifica degli ordini sia per l'Acquisto che per la Vendita.
4.2. Esempio di Utilizzo di CExpertAdvisor - Expert Senza Indicatori, Analisi dello Stato della Posizione e del Risultato
Per dimostrarlo. prendiamo il sistema che scambia solo sulla posizione inversa con aumento del lotto dopo la perdita (questo tipo di expert è solitamente chiamato "Martingale")
a) Effettuare l'ordine iniziale
- quando l'expert inizia, apre la prima posizione per Acquistare con il lotto iniziale
b) Aprire posizioni successive
- se la posizione precedente è stata chiusa in profitto, allora aprire la posizione nella stessa direzione con il lotto iniziale
- se la posizione precedente è stata chiusa in perdita, aprire la posizione nella direzione opposta con un lotto più grande (usando il fattore).
Per il nostro expert, avremo bisogno di tre funzioni della classe CExpertAdvisor:
- posizione aperta - DealOpen()
- ottenere il valore di profitto della posizione chiusa per ticket di posizione - CountProfitByDeal()
- moduli di lavoro - OpenPosition(), CheckPosition()
Poiché l'expert non analizza alcun indicatore, ma solo i risultati delle offerte, utilizzeremo gli eventi OnTrade () per la produttività ottimale. In sostanza, l'expert, che una volta effettuato il primo ordine iniziale per Acquistare, elencherà tutti gli ordini successivi solo dopo la chiusura di questa posizione. Quindi effettueremo l'ordine iniziale in OnTick() e svolgeremo tutto il lavoro successivo in OnTrade().
La funzione Init(), come al solito, inizializza semplicemente i parametri della classe con parametri esterni dell'expert.
Il modulo OpenPosition() apre la posizione iniziale ed è bloccato dal flag m_first.
Il modulo CheckPosition() controlla le ulteriori inversioni della posizione.
Questi moduli sono chiamati dalle rispettive funzioni dell'expert: OnTick() e OnTrade().
//+------------------------------------------------------------------+ //| eMarti.mq5 | //| Copyright Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" #include <Trade\DealInfo.mqh> input double Lots = 0.1; // Lot input double LotKoef = 2; // lot multiplier for loss input int Dist = 60; // distance to Stop Loss and Take Profit //--- class CMartiEA : public CExpertAdvisor { protected: double m_lots; // Lot double m_lotkoef; // lot multiplier for loss int m_dist; // distance to Stop Loss and Take Profit CDealInfo m_deal; // last deal bool m_first; // flag of opening the first position public: void CMartiEA() { } void ~CMartiEA() { } virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // initialization virtual void OpenPosition(); virtual void CheckPosition(); }; //------------------------------------------------------------------ Init bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist; // copy parameters m_deal.Ticket(0); m_first=true; m_bInit=true; return(true); // trade allowed } //------------------------------------------------------------------ OnTrade void CMartiEA::OpenPosition() { if(!CExpertAdvisor::Main()) return; // call parent function if(!m_first) return; // if already opened initial position ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position if(deal>0) { m_deal.Ticket(deal); m_first=false; } // if position exists } //------------------------------------------------------------------ OnTrade void CMartiEA::CheckPosition() { if(!CExpertAdvisor::Main()) return; // call parent function if(m_first) return; // if not yet placed initial position if(PositionSelect(m_smb)) return; // if position exists // check profit of previous position double lot=m_lots; // initial lot long dir=m_deal.Type(); // previous direction if(CountProfitByDeal(m_deal.Ticket())<0) // if there was loss { lot=NormalLot(m_lotkoef*m_deal.Volume()); // increase lot dir=ReversType(m_deal.Type()); // reverse position } ulong deal=DealOpen(dir,lot,m_dist,m_dist); // open position if(deal>0) m_deal.Ticket(deal); // remember ticket } CMartiEA ea; // class instance //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // initialize expert return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.OpenPosition(); // process tick - open first order } //------------------------------------------------------------------ OnTrade void OnTrade() { ea.CheckPosition(); // process trade event }
5. Lavorare Con gli Eventi
In questo articolo, hai incontrato esempi di elaborazione di due eventi: NewTick e Trade che erano rappresentati rispettivamente dalle funzioni OnTick() e OnTrade(). Nella maggior parte dei casi, questi due eventi vengono utilizzati costantemente.
Per gli expert, esistono quattro funzioni di elaborazione degli eventi:
- OnChartEvent elabora un ampio gruppo di eventi: quando si lavora con oggetti grafici, tastiera, mouse ed eventi personalizzati. Ad esempio, la funzione viene utilizzata per creare expert o expert interattivi, costruiti sul principio della gestione grafica degli ordini. Più semplicemente, per creare controlli attivi dei parametri del programma MQL (utilizzando pulsanti e campi di modifica). In generale, questa funzione viene utilizzata per elaborare eventi esterni di un expert.
- OnTimer viene chiamato quando viene elaborato l'evento timer di sistema. Viene utilizzato nei casi in cui il programma MQL richiede di analizzare il suo ambiente su base regolare -per calcolare i valori degli indicatori- quando è necessario fare continuamente riferimento a fonti esterne di segnali, ecc. In parole povere, la funzione OnTimer() è un'alternativa, persino il miglior sostituto per:
while(true) { /* perform analysis */; Sleep(1000); }.
Vale a dire che l'expert non deve lavorare in un ciclo infinito al suo avvio, ma abbastanza per spostare le chiamate delle sue funzioni da OnTick() a OnTimer(). - OnBookEvent elabora un evento che viene generato quando la Profondità del Mercato cambia il suo stato. Questo evento può essere attribuito all'esterno ed se ne può effettuare l’elaborazione in linea con l'attività.
- OnTester viene chiamato dopo aver testato l'expert su un determinato intervallo di date, prima della funzione OnDeinit() per l'eventuale screening delle generazioni di test, quando si utilizza l'ottimizzazione genetica mediante il parametro Custom max.
Non dimenticare che è sempre consigliabile utilizzare eventuali eventi e le loro combinazioni per la risoluzione del loro compito specifico.
Postfazione
Come puoi vedere, scrivere un expert avendo lo schema giusto, non richiede molto tempo. A causa delle nuove possibilità di elaborare gli eventi in MQL5, abbiamo una struttura più flessibile di gestione del processo di trading. Tuttavia tutti questi elementi diventano uno strumento davvero potente solo se hai preparato correttamente i tuoi algoritmi di trading.
L'articolo descrive tre principi fondamentali della loro creazione: movimentazione, astrazione, modularità. Renderai il tuo trading più semplice se baserai i tuoi expert su questi "tre pilastri".
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/132





- 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