Il Prototipo di Robot di Trading

--- | 9 dicembre, 2021

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.

Questi tre principi consentono di creare un unico prototipo per tutti gli expert che è possibile modificare e adattare facilmente a qualsiasi compito o attività.

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.

Tutta la varietà di expert si basa sulla combinazione di queste tre fonti di eventi

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

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.

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.

Raccomandazione: Quando descrivi il sistema di trading, non iniziare con parole del tipo "1. Ottieni il segnale ... apri ordine", ma da suddividere immediatamente in sezioni: "a) La condizione di apertura degli ordini, b) Condizioni di manutenzione degli ordini, ecc." e in ciascuna per analizzare i segnali richiesti.

Per comprendere meglio questo approccio, ecco i diversi schemi di lavoro nei contesti di quattro diversi expert.

Figura 2. Esempi di implementazione degli 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

Come si può vedere dagli schemi, qualsiasi sistema di trading è molto facile da descrivere usando la logica diretta


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

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 Indicatori

Come 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:

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

b) Aprire posizioni successive

Per il nostro expert, avremo bisogno di tre funzioni della classe CExpertAdvisor:

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:

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".