English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Scrivere un Expert Advisor Utilizzando l'Approccio di Programmazione Orientato agli Oggetti MQL5

Scrivere un Expert Advisor Utilizzando l'Approccio di Programmazione Orientato agli Oggetti MQL5

MetaTrader 5Esempi | 16 dicembre 2021, 10:51
645 0
Samuel Olowoyo
Samuel Olowoyo

Introduzione

Nel primo articolo, abbiamo fatto un viaggio attraverso i passaggi di base della creazione, del debug e del test di un Expert Advisor in MQL5.

Tutto ciò che abbiamo fatto è stato molto semplice e interessante; tuttavia, il nuovo linguaggio MQL5 ha molto di più da offrire. In questo articolo, considereremo l'approccio Orientato agli Oggetti per fare ciò che abbiamo fatto nel primo articolo. La maggior parte delle persone pensa che sia difficile ma voglio assicurarti che quando avrai finito di leggere questo articolo, sarai in grado di scrivere il tuo Expert Advisor basato sugli oggetti.

Non ripeteremo alcune delle cose che abbiamo imparato nel primo articolo, quindi ti consiglio di leggere prima di tutto l'articolo se non lo hai già fatto.


1. Il Paradigma Orientato agli Oggetti

Una delle cose che rende il nuovo MQL5 molto più potente e robusto di MQL4 è il suo OOP (Object Oriented Programming).

Si consiglia in OOP che un oggetto non esponga nessuno dei suoi dettagli di implementazione. In questo modo, la sua implementazione può essere modificata senza modificare il codice che utilizza l'oggetto. Ciò significa che una classe consente a un programmatore di nascondere (e impedisce anche di modificare) il modo in cui viene implementata la classe che ha scritto. 

Per rendere le cose più chiare, soffermiamoci un po' sui termini "classe" e "oggetto" appena citati.

  • CLASS. Una classe è più simile a un concetto espanso di una struttura di dati, ma invece di contenere solo dati, contiene sia dati che funzioni. Una classe può contenere diverse variabili e funzioni, chiamate membri della classe. È un incapsulamento di membri dati e funzioni che manipolano i dati. Una classe è molto più potente poiché puoi racchiudere in essa tutte le tue funzioni di Expert Advisor. Farai riferimento alle funzioni ogni volta che ne avrai bisogno nel tuo codice EA. A proposito, questo è l'argomento di questo articolo.
  • OBJECT. Un oggetto è un'istanza di una classe. Una volta che una classe è stata creata, per usarla, dobbiamo dichiarare un'istanza di classe. Questo è chiamato un oggetto. In altre parole, per creare un oggetto serve una classe.

1.1. DICHIARARE UNA CLASSE

Una classe, fondamentalmente, contiene la descrizione dei membri (proprietà e funzioni/metodi) di un oggetto che si desidera creare dalla classe. Facciamo un esempio...

Se vogliamo creare un oggetto che abbia porte, sedili, pneumatici, peso, ecc. e che possa anche partire, cambiare marcia, fermarsi e suonare il clacson; allora abbiamo bisogno di scrivere una classe. Le porte, i sedili, le gomme, il peso, l'avviamento, il cambio, l'arresto e il clacson saranno i membri della classe.

Naturalmente, osserverai che questi membri sono classificati; alcuni sono proprio ciò che avrà il nostro oggetto (proprietà) mentre gli altri sono ciò che farà il nostro oggetto (azioni – funzioni/metodi). Per dichiarare la nostra classe, dobbiamo pensare a un nome molto valido e descrittivo. In questo caso chiameremo la nostra classe CAR. La nostra classe CAR avrà le proprietà e le funzioni sopra indicate come suoi membri.

Per dichiarare una classe, iniziamo digitando la parola chiave class seguita dal nome della classe stessa seguito a sua volta da una coppia di parentesi graffe che contiene i membri della classe.

Quindi, il formato di base di una classe è come mostrato di seguito:

class class_name 
{
  access_keyword_1:
    members1;

  access_keyword_2:
    members2;
  ...
};

Qui, nome_classe è un identificatore valido per la classe che vogliamo scrivere, membri1 e membri2 sono i membri dati della classe.

L'access_keyword specifica il diritto di accesso ai membri della nostra classe.  Una parola chiave di accesso può essere privata, protetta o pubblica. Ricorda che stiamo cercando di scrivere una classe che possa essere utilizzata da noi stessi e da altri senza esporre effettivamente i dettagli dell'implementazione. Ecco perché i diritti di accesso sono necessari.

Potrebbero esserci alcuni membri della nostra classe a cui non vogliamo accedere dall'esterno della nostra classe. Questi sono dichiarati all'interno della sezione di accesso privato utilizzando la parola chiave privata o protetta. Gli altri membri a cui vogliamo accedere dall'esterno della nostra classe verranno quindi dichiarati all'interno della sezione di accesso pubblico utilizzando la parola chiave pubblica. Ora, la nostra nuova classe CAR avrà il seguente aspetto:

class CAR 
{
  private:
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

La nostra classe CAR viene dichiarata utilizzando la parola chiave class. Questa classe contiene otto membri di cui quattro con accesso privato e quattro con accesso pubblico. I quattro membri nella sezione privata sono membri dati. Tre sono di tipo intero (int) e uno di tipo double. Non è possibile accedere a questi membri da qualsiasi altra funzione dichiarata all'esterno di questa classe. 

I quattro membri della sezione pubblica sono membri di funzione. Due restituiscono il tipo di dati bool e due restituiscono il tipo void. Questi sono i membri accessibili a qualsiasi oggetto di questa classe ogni volta che viene creato da chiunque la utilizzi. Una volta creato un oggetto della nostra classe, questi membri saranno prontamente disponibili per l'uso.

Come giustamente osserverete, le parole chiave di accesso (privata, pubblica, protetta) sono sempre seguite da due punti. Anche la dichiarazione di classe terminava con un punto e virgola. I membri vengono dichiarati utilizzando il tipo di dati corretto.

Prima di tutto va notato che: una volta dichiarata una classe, a tutti i membri di quest’ultima vengono dati diritti di accesso privati a meno che non sia esplicitamente specificato come abbiamo fatto sopra. Per esempio nella dichiarazione di classe di seguito:

class CAR 
{
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

Per tutti e quattro i membri dichiarati sopra la parola chiave di accesso pubblico ha automaticamente accesso privato.

Per far sì che la nostra classe venga utilizzata, occorre prima di tutto creare un oggetto della classe. Ora, creiamo un oggetto che è un tipo della nostra classe. Per fare ciò useremo il nome della nostra classe seguito dal nome che vogliamo dare all'oggetto.

CAR Honda;

Oppure possiamo creare un altro oggetto

CAR Toyota;

Honda o Toyota ora sono un tipo di una CAR e ora può avere accesso a tutte le funzioni membro della nostra classe CAR a condizione che le funzioni membro siano dichiarate all'interno della sezione di accesso pubblico. Noi torneremo su questo più tardi.

Puoi notare che possiamo creare quanti oggetti di questa classe vogliamo. Questo è uno dei vantaggi della programmazione Orientata agli Oggetti.

A questo punto, consideriamo nel dettaglio il formato di una classe in MQL5.

class class_name 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

Questa è una dichiarazione della classe dove class_name è il nome della stessa. Questa classe ha nove membri, ma di questi nove, due sono membri speciali.

Il Constructor:
Il constructor (rappresentato come class_name()) è una funzione speciale che viene chiamata automaticamente quando viene creato un nuovo oggetto del tipo della classe. Quindi in questo caso, quando crei un oggetto del tipo di questa classe

class_name oggetto;

il constructor, class_name(), viene chiamato automaticamente. Il nome del constructor deve corrispondere al nome della classe, ecco perché abbiamo chiamato il constructor come class_name(). In MQL5, un constructor non accetta parametri di input e non ha un tipo restituito. Le allocazioni di memoria e l'inizializzazione dei membri della classe vengono normalmente eseguite quando viene chiamato il constructor. I constructor non possono essere chiamati esplicitamente come se fossero normali funzioni membro. Vengono eseguiti solo quando viene creato un nuovo oggetto di quella classe. Una classe in MQL5 può avere un solo constructor.

Il Destructor:
Il secondo membro speciale è rappresentato come ~class_name(). Questo è il destructor di classe scritto con un’onda (~) prima del nome della classe. Viene chiamato automaticamente quando un oggetto classe viene distrutto. Tutti i membri della classe che devono essere re-inizializzati vengono re-inizializzati in questa fase e non importa se hai dichiarato esplicitamente o meno il destructor.

Membri Dati:
I membri di una classe possono essere qualsiasi tipo di dati legale, il tipo di classe o il tipo di struttura. In altre parole, quando si dichiarano le variabili membro di una classe, è possibile utilizzare qualsiasi tipo di dato legale (int, double, string, ecc.), un oggetto di un'altra classe o un tipo di struttura (ad esempio, MQL5 MqlTradeRequest, ecc.)

Membri della Funzione:
Questi sono membri della classe che vengono utilizzati per modificare i membri dei dati ed eseguire le principali funzioni/metodi della classe. Il tipo restituito per i membri della funzione può essere di qualsiasi tipo legale restituito (bool, void, double, string, ecc.).

Privata:
I membri dichiarati in questa sezione sono accessibili solo dai membri della funzione della classe. Non è possibile accedervi da nessun'altra funzione al di fuori della classe.

Protetta:
I membri dichiarati in questa sezione sono accessibili ai membri della funzione della classe e sono accessibili anche alle funzioni membro di altre classi derivate da questa classe. Ciò significa che possiamo anche creare una nuova classe da quest’ultima. In questo caso, la nuova classe derivata da questa classe (che diventerà ora quella base) potrà accedere ai membri protetti della classe base. Questo è il concetto di ereditarietà in OOP. Ne parleremo presto, rilassati...

Pubblica:
Membri dichiarati all'interno di questa sezione sono disponibili per l'utilizzo al di fuori della classe da un oggetto della classe. Qui è dove si dichiarano alcune delle funzioni che saranno necessarie per utilizzare la classe in altri programmi.

Ora che abbiamo esaminato il formato base di una classe, spero che non vi siate ancora annoiati perché abbiamo ancora qualche altro aspetto interessante delle classi che dobbiamo esaminare prima di passare finalmente alla creazione di un wrapper di classe per il nostro Expert Advisor.

1.2. EREDITARIETÀ

Supponiamo che vogliamo creare un'altra classe da questa classe iniziale base_class. Il formato per derivare una nuova classe da una classe iniziale è il seguente:

Classe Base:

class base_class 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

La Classe Derivata:

class new_class : access_keyword base_class 
{
  private:
    members8;

  public:
    new_class()  //Constructor;
    ~new_class() //Destructor;
    Members9();
};

Alcune spiegazioni prima di procedere alla spiegazione dei dettagli. La classe new_class è derivata dalla classe base_class usando i due punti e una parola chiave di accesso come mostrato sopra. Ora, la nuova_classe derivata/creata da base_class può accedere (o ereditare) sia i membri pubblici che protetti di base_class ma non può accedere (o non ereditare) i membri privati della base_class. La new_class può anche implementare nuovi metodi/funzioni di membri diversi dalla base_class. In altre parole, la new_class può anche avere i propri dati e membri di funzione oltre a quelli che eredita dalla base_class.

Se la parola chiave public viene utilizzata nella creazione della classe derivata, significa che i membri pubblici e protetti della classe base verranno ereditati come membri pubblici e protetti della classe derivata. Se viene utilizzata la parola chiave Protected, i membri public e protected della classe base verranno ereditati come membri protetti della classe derivata. Se viene utilizzata la parola chiave private, i membri public e protected della classe base verranno ereditati come membri privati della classe derivata.

È importante notare che quando viene creato un nuovo oggetto della new_class (la classe derivata), il constructor della base_class viene chiamato prima del constructor della new_class; mentre quando l'oggetto viene distrutto, il destructor della new_class (la classe derivata) viene chiamato prima del destructor della base_class.

Per comprendere meglio questo concetto di ereditarietà, torniamo alla nostra classe iniziale CAR.

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();

  private:
    int        tyres;
};

Possiamo derivare un'altra classe SALOON da questa classe. Si noti che ho dichiarato protetti tre dei membri dati della classe CAR. Questo per consentire alla nostra nuova classe SALOON di ereditare questi membri.

Inoltre, voglio che tu capisca che l'ordine in cui inserisci le parole chiave di accesso non ha importanza. Ciò che conta è che tutti i membri dichiarati sotto una parola chiave di accesso appartengano a quella parola chiave.

class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void       runathighspeed();
};

La nostra classe derivata SALOON ha due membri e allo stesso tempo eredita sette membri (membri protetti e membri pubblici) dalla classe base CAR. Ciò significa che una volta creato un oggetto di SALOON, sarà in grado di accedere alle funzioni membro pubbliche di CAR che sono start(), changegear(), stop() e horn() insieme alla propria funzione membro pubblica runathighspeed() . Questo è il concetto di ereditarietà.

Proprio come alcuni caratteri/comportamenti (metodi) dei nostri padri/genitori (classe base) si manifestano in noi, nei loro figli (classe derivata), perché ereditiamo quei comportamenti (metodi/funzioni) da loro geneticamente o meno. Mi dispiace, non sono un medico, ma credo che tu afferri bene il quadro che sto cercando di dipingere. A proposito, MQL5 non supporta l'ereditarietà multipla, quindi non c'è bisogno di parlarne.

Hmm!!! Spero che il panno nero che copre la cosa mistica chiamata OOP o CLASS venga rimosso a poco a poco... non ti stancare, se a questo punto senti di non essere ancora molto chiaro con ciò di cui stiamo discutendo, potresti aver bisogno di rilassarti, prendi una tazza di caffè e poi torna indietro e ricomincia dall'inizio. Non è così misterioso come pensi...

Se sei tornato a questo punto, presumo che tu stia seguendo la mia spiegazione. Voglio che tu mi dica quante altre classi puoi derivare dalla nostra classe base CAR? Per favore ho bisogno che tu risponda. Sono serio. Nominali, scrivi le loro dichiarazioni e spediscimele. Se puoi nominarli tutti, ti porterò fuori a pranzo... (sto scherzando?)

Ora che sei pronto a saperne di più, continuiamo...

È vero che quando scrivo, scrivo come mio padre. La sua scrittura a mano è molto pulita e elegante, proprio come la mia. Immagino sia qualcosa che ho ereditato da lui, ma indovina un po'; lui usa la sua mano sinistra per scrivere mentre io uso la destra e quando vedi le scritte difficilmente puoi differenziare perché sembrano simili. Qual è il problema qui? Eredito la buona calligrafia da mio padre ma non scrivo con la mano sinistra come lui. Ciò significa che anche se è ciò che eredito e sembra simile, ma il modo in cui faccio il mio è diverso da mio padre. Ha senso per te? Questa è un'idea di ciò che viene chiamato polimorfismo in OOP.

Una classe derivata (me stesso, come nell'esempio sopra) eredita una funzione membro (writefine() - per la mia scrittura a mano) da una classe base (mio padre) ma (I) implementa la funzione (writefine() ) in un modo diverso dalla classe base (mio padre).

Torniamo alla nostra classe CAR, e alla classe derivata SALOON;

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool               start();
    virtual void       changegear(){return(0);}
    void               stop();
    bool               horn();

  private:
    int        tyres;
};
class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void               runathighspeed();
    virtual  void       changegear(){gear1=reverse; gear2=low; gear3=high;}
  };

class WAGON : public CAR 
{
  private:
    bool               hasliftback;

  public:
   virtual  void       changegear(){gear1=low; gear2=high; gear3=reverse;}
};

Diamo un'occhiata ad alcune modifiche che abbiamo apportato qui. Innanzitutto, abbiamo dichiarato una nuova classe derivata da CAR denominata WAGON con due membri. Abbiamo anche modificato la funzione membro changegear()per diventare una funzione virtuale nella classe base. Perché abbiamo reso changegear() una funzione virtuale. È semplicemente perché vogliamo che qualsiasi classe che erediti la funzione dalla classe base sia in grado di implementarla a modo suo.

In altre parole, le funzioni membro virtuali di una classe sono funzioni membro che possono essere sovrascritte o implementate in modo diverso in qualsiasi classe derivata dalla classe in cui sono dichiarate. Il corpo della funzione membro può quindi essere sostituito con un nuovo set di implementazione nella classe derivata. Anche se non possiamo usare di nuovo la parola virtuale nelle classi derivate, è buona pratica di programmazione usarla sempre nelle classi derivate.

Dagli esempi sopra, le classi SALOON e WAGON implementano la funzione changegear() a modo loro.

1.3. DEFINIZIONE DEI METODI DI CLASSE (FUNZIONI DEI MEMBRI)

Poiché abbiamo saputo, in qualche modo, come dichiarare le classi; andiamo oltre discutendo come definire le funzioni membro di una classe. Dopo aver dichiarato la classe, il passo successivo è definire le funzioni membro della nostra classe. Guardiamo di nuovo la nostra classe CAR

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    void       CAR() // Constructor
    bool       start();
    void       changegear();
    void       stop();
    bool       horn(){press horn;}

  private:
    int        tyres;
};

 void CAR::CAR()
{
 // initialize member variables here
}

bool CAR::start()
{
 // car start procedure here
}

void CAR::changegear()
{
// car changegear procedure here
}

void CAR::stop()
{
// car stop procedure here
}

Nella definizione delle funzioni membro, abbiamo utilizzato un operatore di due punti (::) chiamato scope operator. Questo è scritto proprio come le normali funzioni, l'unica differenza è il nome della classe e lo scope operator che viene aggiunto. Osserverai anche che una delle funzioni era già definita all'interno della classe (funzione membro horn()). Una funzione membro può essere definita nella dichiarazione di classe o al di fuori della dichiarazione di classe come l'hai vista qui.

Penso che sarà importante rivedere un po' il concetto di funzioni prima di procedere.

1.4. FUNZIONI

A proposito, cos'è una funzione?

A volte in una casa dove hai tre figli, invece di far fare a uno solo di loro tutti i lavori in casa; a uno è stato chiesto di lavare i piatti tutti i giorni dopo cena, a uno è stato chiesto di spazzare mentre al terzo è stato dato il compito di sistemare i letti ogni mattina.

Ci sono dei lavori da fare in casa, invece di dare tutti i lavori a un bambino, li abbiamo divisi tra loro tre. Questo renderà il compito molto facile e leggero per ognuno di loro piuttosto che essere un peso per uno solo di loro. Inoltre, se uno dei bambini non ha svolto il suo compito, sappiamo rapidamente quale di loro frustare. Questa è l'idea alla base delle funzioni.

La maggior parte delle volte vogliamo scrivere un codice che svolgerà molte attività. È qui che entrano in gioco le funzioni. Possiamo decidere di suddividere l'attività in attività più piccole e quindi scrivere una funzione per eseguire ciascuna delle attività più piccole.  Una funzione è un blocco di codice che esegue o implementa un insieme di operazioni. È un gruppo di istruzioni che viene eseguito ogni volta che viene chiamato da qualche punto in un programma.

Una funzione può essere definita come segue:

Return_type function_name (parameters1,parameters2,…)
{
  Expressions; //(actions to carry out by the function)
}
  • Return_type: il tipo di dati restituito dalla funzione (deve essere un tipo di dati valido o void se restituisce niente)
  • Function_name: il nome della funzione (deve essere un nome valido) che verrà utilizzato per chiamare la funzione
  • Parametri: i parametri sono variabili valide di tipo dati che agiranno all'interno della funzione come una variabile locale. Se una funzione ha più di un parametro, sono separati da virgole.
  • Espressioni: il corpo della funzione che contiene il blocco di istruzioni

Esempio di una funzione:

int doaddition (int x, int y)
{
 return (x+y);
}

Il tipo restituito dalla funzione è intero (int), doaddition è il nome della funzione e int x e int y sono i parametri. Ciò che fa la funzione è aggiungere due parametri di input forniti e restituire il risultato. Quindi, se forniamo alla funzione due variabili intere 2 e 3, la funzione eseguirà l'addizione e restituirà 5 come risultato.

int doaddition(2,3) // returns 5

Per ulteriori informazioni sulle funzioni, consultare il manuale di riferimento MQL5.

Ora basta con le teorie mettiamoci al lavoro.

L’ essenza di questo articolo è insegnarti come puoi scrivere una lezione per il tuo Expert Advisor che utilizza l'approccio Object Oriented presentato in MQL5.

È tempo di agire adesso...

2. Scrivere un Expert Advisor

A questo punto, faremo riferimento all'Expert Advisor che abbiamo creato nel primo articolo. Se non hai letto l'articolo, per favore fallo ora in modo che la maggior parte delle cose di cui parleremo da questo punto in poi non ti siano estranee. Tuttavia, potrei ancora rivedere alcune cose che potrebbero essere necessarie.

Prima di poter scrivere la tua lezione, devi prima sederti e sviluppare la tua strategia di trading. Lo abbiamo già fatto nel primo articolo. La prossima cosa è selezionare quelle funzionalità che vogliamo delegare alla nostra classe. Queste funzionalità determineranno le variabili membro della nostra classe. Solo un riassunto della nostra strategia di trading dal primo articolo.

Cosa farà il nostro EA:

  • Monitorerà un particolare indicatore e, quando viene soddisfatta una certa condizione (o determinate condizioni), piazzerà un trade (o Short/Sell o Long/Buy), a seconda della condizione attuale che è stata soddisfatta.

Quanto sopra è chiamato una strategia di trading. Prima di poter scrivere un EA, devi prima sviluppare la strategia che desideri automatizzare nell'EA. Quindi, in questo caso, modifichiamo l'affermazione di cui sopra in modo che rifletta la strategia che vogliamo sviluppare in un EA.

  • Useremo un indicatore chiamato Moving Average con un periodo di 8 (puoi scegliere qualsiasi periodo, ma ai fini della nostra strategia, useremo 8)
  • Vogliamo che il nostro EA piazzi un trade Long (Buy) quando la Moving Average-8 (per il bene della nostra discussione, mi riferirò ad essa come MA-8) è in aumento e il prezzo è vicino sopra di essa e si posizionerà uno Short (Sell) quando MA-8 sta diminuendo al ribasso e il prezzo è vicino ad esso.
  • Utilizzeremo anche un altro indicatore chiamato Average Directional Movement (ADX) con periodo 8 anche per aiutarci a determinare se il mercato è in trend o meno. Lo stiamo facendo perché vogliamo entrare nel trading solo quando il mercato è in trend e rilassarci quando il mercato è in range (cioè, non in trend). Per raggiungere questo obiettivo, posizioneremo la nostra operazione (Buy o Sell) solo quando le condizioni di cui sopra sono soddisfatte e il valore ADX è maggiore di 22. Se ADX è maggiore di 22 ma diminuisce, o ADX è minore di 22, non faremo trading, anche se la condizione B è stata soddisfatta.
  • Vogliamo proteggerci anche impostando uno Stop loss di 30 pips, e per il nostro obiettivo di Profitto; mireremo a un profitto di 100 pips.
  • Vogliamo anche che il nostro EA cerchi opportunità di Buy/Sell solo quando è stata formata una nuova barra e ci assicureremo anche di aprire una posizione Buy se le condizioni di acquisto sono soddisfatte e non ne abbiamo già una aperta e apriremo una posizione di vendita posizione quando le condizioni Sell sono soddisfatte e non ne abbiamo già una aperta.

Inoltre, vogliamo assicurarci di essere in grado di controllare la percentuale del nostro margine gratuito che può essere utilizzato per piazzare un trade e anche di controllare il margine libero disponibile prima di piazzare qualsiasi trade. Il nostro EA effettuerà un trade solo se il margine disponibile è sufficiente per il trade.

Adesso hai capito cosa vogliamo fare. Le funzioni che vogliamo delegare alle nostre classi sono:

  • Controlla le condizioni di Buy e Sell
  • Posiziona Buy/Sell a seconda del risultato delle condizioni controllate

Fondamentalmente, questo è tutto ciò che vogliamo che il nostro EA faccia. Queste due funzionalità sono le funzioni principali, ma ce ne sono ancora di più. Ad esempio, nel controllare le posizioni di Buy/Sell, è necessario utilizzare gli indicatori. Questo significa che ottenere i valori degli indicatori deve essere anche nella nostra classe. Quindi, includiamo:

  • Ottieni tutte gli handler degli indicatori (nella sezione EA OnInit)
  • Ottieni tutti i buffer degli indicatori (nella sezione EA OnTick)
  • Rilascia tutte gli handler degli indicatori (nella sezione EA OnDeinit)

Per ottenere i valori dell'indicatore, la nostra classe dovrà conoscere i periodi MA e ADX, il periodo del grafico e il simbolo (coppia di valute con cui stiamo lavorando), quindi dobbiamo includere anche:

  • Ottieni periodi ADX e MA e altri parametri importanti come il periodo e il simbolo del grafico.

Anche per il controllo del margine libero prima di piazzare un trade, includeremo

  • Controlla Gratis il Margine/percentuale del conto da utilizzare per il trading

Con questo abbiamo già un'idea di quali variabili e funzioni dovrebbero essere nella nostra classe.

Ok, ho pensato io per te; è il momento di scrivere il codice.

2.1. Scrivere una classe

Cominciamo lanciando il MetaEditor (credo che tu lo sappia già). Una volta aperto il MetaEditor, iniziamo un nuovo documento MQL facendo clic sulla Nuova barra degli strumenti o Ctrl+N. Nella finestra della procedura guidata, seleziona "Include" e fai clic sul pulsante NEXT.

Figura 1. Avvio di un nuovo documento MQL5

Figura 1. Avvio di un nuovo documento MQL5

Digita il nome del file come mostrato di seguito e fai clic su finish:

Figura 2. Denominazione di un nuovo documento

Figura 2. Denominazione di un nuovo documento

Abbiamo selezionato include perché la nostra classe sarà un file di inclusione che verrà inserito nel nostro codice EA una volta che saremo pronti per usarlo. Ecco perché non hai spazio per inserire i parametri di input.

Come al solito, l'editor ti fornisce uno scheletro di ciò che pensa che tu voglia fare.


Per iniziare, cancella tutto ciò che si trova sotto la riga di codice "#property link …". Dovresti ora avere qualcosa del genere.

//+------------------------------------------------------------------+
//|                                              my_expert_class.mqh |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"

Adesso scriviamo la dichiarazione della nostra classe, la chiameremo MyExpert.

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{

Analizziamo la dichiarazione di classe. La dichiarazione inizia con il nome della classe. Successivamente abbiamo dichiarato i membri privati della classe.

I Membri Privati:

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{
//--- private members
private:
   int               Magic_No;   // Expert Magic Number
   int               Chk_Margin; // Margin Check before placing trade? (1 or 0)
   double            LOTS;       // Lots or volume to Trade
   double            TradePct;   // Percentage of Account Free Margin to trade
   double            ADX_min;    // ADX Minimum value
   int               ADX_handle; // ADX Handle
   int               MA_handle;  // Moving Average Handle
   double            plus_DI[];  // array to hold ADX +DI values for each bars
   double            minus_DI[]; // array to hold ADX -DI values for each bars
   double            MA_val[];   // array to hold Moving Average values for each bars
   double            ADX_val[];  // array to hold ADX values for each bars
   double            Closeprice; // variable to hold the previous bar closed price 
   MqlTradeRequest   trequest;    // MQL5 trade request structure to be used for sending our trade requests
   MqlTradeResult    tresult;     // MQL5 trade result structure to be used to get our trade results
   string            symbol;     // variable to hold the current symbol name
   ENUM_TIMEFRAMES   period;      // variable to hold the current timeframe value
   string            Errormsg;   // variable to hold our error messages
   int               Errcode;    // variable to hold our error codes

Come spiegato in precedenza, queste variabili membro private non sono accessibili da alcuna funzione al di fuori della classe. La maggior parte delle variabili sono molto chiare nelle loro dichiarazioni, quindi non perderò tempo a parlarne.

Tuttavia, ricorderete nella nostra discussione che abbiamo affermato che le variabili membro possono essere qualsiasi tipo di dati, struttura o classe legale.

Credo che tu possa vederlo in azione qui con la dichiarazione dei tipi MqlTradeRequest e MqlTradeResults.

Il Constructor

//--- Public member/functions
public:
   void              MyExpert();                                  //Class Constructor

Il constructor non accetta parametri di input; tienilo a mente quando scrivi la tua classe.

Le funzioni dei membri

//--- Public member/functions
public:
   void              MyExpert();                                 //Class Constructor
   void              setSymbol(string syb){symbol = syb;}         //function to set current symbol
   void              setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period
   void              setCloseprice(double prc){Closeprice=prc;}   //function to set prev bar closed price
   void              setchkMAG(int mag){Chk_Margin=mag;}          //function to set Margin Check value
   void              setLOTS(double lot){LOTS=lot;}               //function to set The Lot size to trade
   void              setTRpct(double trpct){TradePct=trpct/100;}   //function to set Percentage of Free margin to use for trading
   void              setMagic(int magic){Magic_No=magic;}         //function to set Expert Magic number
   void              setadxmin(double adx){ADX_min=adx;}          //function to set ADX Minimum values

Abbiamo definito queste funzioni membro per consentirci di impostare le variabili importanti che saranno necessarie alla nostra classe per svolgere la sua funzione. Senza l'utilizzo di queste funzioni, queste variabili non saranno disponibili per l'utilizzo da parte della nostra classe. Come noterai anche, avevamo già dichiarato una variabile corrispondente nella nostra classe che conterrà questi valori una volta impostati da queste funzioni.

Un'altra cosa da notare è che abbiamo definito queste funzioni membro all'interno della dichiarazione di classe. Come ho spiegato prima, è consentito. Significa che non avremo bisogno di definirli di nuovo quando si definiscono altre funzioni membro, come vedrai molto presto.

Proprio come le normali funzioni, hanno parametri del tipo di dati corretto a seconda dei valori restituiti da ciascuna funzione. Credo che questo non dovrebbe risultare strano per te.

void              doInit(int adx_period,int ma_period);         //function to be used at our EA intialization
void              doUninit();                                  //function to be used at EA de-initializatio
bool              checkBuy();                                  //function to check for Buy conditions
bool              checkSell();                                 //function to check for Sell conditions
void              openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,
                         double TP,int dev,string comment="");   //function to open Buy positions
void              openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,
                          double TP,int dev,string comment="");  //function to open Sell positions

Noi dichiariamo solo queste funzioni membro ma non le abbiamo definite. Questo è perché lo faremo più tardi. Queste sono funzioni che manipoleranno la maggior parte dei valori memorizzati nelle variabili membro della nostra classe e allo stesso tempo essi formano le funzioni per il ruolo principale della nostra classe. Li discuteremo più tardi.

I membri Protetti

Questi membri verranno ereditati da qualsiasi classe derivata dalla nostra classe. Non è realmente necessario se non si intende derivare nessun'altra classe da questa classe. Puoi anche metterli come membri privati. Lo sto facendo solo per farti capire i vari problemi che abbiamo discusso in precedenza sulle classi.

//--- Protected members
protected:
   void              showError(string msg, int ercode);   //function for use to display error messages
   void              getBuffers();                       //function for getting Indicator buffers
   bool              MarginOK();                         //function to check if margin required for lots is OK

Anche queste tre funzioni sono molto importanti sebbene interne alla nostra classe. Lo showError mostrerà i nostri errori e i getBuffers saranno usati per ottenere i buffer degli indicatori. MarginOK controlla se c'è abbastanza margine libero per aprire una posizione.

Una volta che hai finito con la dichiarazione della classe, non dimenticare il punto e virgola. È molto importante.

};   // end of class declaration

La prossima cosa da fare subito dopo aver dichiarato la classe è definire le funzioni membro che non sono state definite nella sezione di dichiarazione.

//+------------------------------------------------------------------+
// Definition of our Class/member functions
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|  This CLASS CONSTRUCTOR
//|  *Does not have any input parameters
//|  *Initilizes all the necessary variables                                          
//+------------------------------------------------------------------+
void MyExpert::MyExpert()
  {
//initialize all necessary variables
   ZeroMemory(trequest);
   ZeroMemory(tresult);
   ZeroMemory(ADX_val);
   ZeroMemory(MA_val);
   ZeroMemory(plus_DI);
   ZeroMemory(minus_DI);
   Errormsg="";
   Errcode=0;
  }

Questo è il nostro constructor di classi.  Qui abbiamo usato i doppi due punti (::) (lo scope operator) tra il nome della classe e il nome della funzione membro. Quello che stiamo cercando di dire è questo:

Sebbene stiamo definendo questa funzione membro al di fuori della dichiarazione di classe, ma è ancora nell'ambito della classe. È un membro della classe il cui nome viene prima dei due punti (scope operator).

 Non ha parametri di input. E’ a questo punto che inizializziamo la maggior parte delle variabili membro necessarie e usiamo la funzione ZeroMemory per farlo.

void  ZeroMemory(
     void & variable      // reset variable
   );

Questa funzione reimposta i valori delle variabili passate ad essa. In questo caso lo usiamo per resettare i valori dei nostri tipi di struttura (MqlTradeRequest e MqlTradeResult) e dei nostri array.

La funzione showError:

 

//+------------------------------------------------------------------+
//|  SHOWERROR FUNCTION
//|  *Input Parameters - Error Message, Error Code                                                               
//+------------------------------------------------------------------+
void MyExpert::showError(string msg,int ercode)
  {
   Alert(msg,"-error:",ercode,"!!"); // display error
  }
 

Questa è una funzione membro protetta che viene utilizzata per visualizzare tutti gli errori riscontrati durante le operazioni di qualsiasi oggetto della nostra classe. Ce ne vogliono due argomenti/parametri – Descrizione dell'errore e codice di errore.

La funzione getBuffers:

//+------------------------------------------------------------------+
//|  GETBUFFERS FUNCTION                                                                
//|  *No input parameters
//|  *Uses the class data members to get indicator's buffers
//+------------------------------------------------------------------+
void MyExpert::getBuffers()
  {
   if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0
      || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0)
     {
      Errormsg="Error copying indicator Buffers";
      Errcode = GetLastError();
      showError(Errormsg,Errcode);
     }
  }
  

Questa funzione viene utilizzata per copiare tutti i nostri buffer degli indicatori negli array che abbiamo specificato nelle variabili membro utilizzando il rispettivo handle dell'indicatore.

La funzione CopyBuffer è stata spiegata nel primo articolo. La funzione getBuffers non ne ha parametri di input perché stiamo usando i valori delle variabili membro della classe.

Abbiamo usato la nostra funzione di errore interna qui per visualizzare qualsiasi errore che potrebbe verificarsi nel processo della copia dei buffer.

La funzione MarginOK:

//+------------------------------------------------------------------+
//|  MARGINOK FUNCTION
//| *No input parameters
//| *Uses the Class data members to check margin required to place a trade
//|  with the lot size is ok
//| *Returns TRUE on success and FALSE on failure
//+------------------------------------------------------------------+
bool MyExpert::MarginOK()
  {
   double one_lot_price;                                                        //Margin required for one lot
   double act_f_mag     = AccountInfoDouble(ACCOUNT_FREEMARGIN);                //Account free margin
   long   levrage       = AccountInfoInteger(ACCOUNT_LEVERAGE);                 //Leverage for this account
   double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);  //Total units for one lot
   string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);        //Base currency for currency pair
                                                                                //
   if(base_currency=="USD")
     {
      one_lot_price=contract_size/levrage;
     }
   else
     {
      double bprice= SymbolInfoDouble(symbol,SYMBOL_BID);
      one_lot_price=bprice*contract_size/levrage;
     }
// Check if margin required is okay based on setting
   if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct))
     {
      return(false);
     }
   else
     {
      return(true);
     }
  }
  
 

Questa funzione sta effettivamente facendo due lavori. Controlla per assicurarci di avere abbastanza margine libero per piazzare il trade e controlla anche per assicurarci di non utilizzare più di una percentuale specificata del margine libero disponibile per piazzare il trade. In questo modo possiamo controllare quanti soldi utilizziamo per ogni trade.

Usiamo la funzione AccountInfoDouble() insieme all'identificatore ENUM_ACCOUNT_INFO_DOUBLE per ottenere il Margine Gratuito per l'account.  Usiamo anche la funzione AccountInfoInteger() insieme all'identificatore ENUM_ACCOUNT_INFO_INTEGER per ottenere la Leva per l'account.  L’AccountInfoInteger() e AccountInfoDouble() sono funzioni account utilizzate per ottenere i dettagli del conto corrente utilizzando l'EA.

double  AccountInfoDouble(
   int  property_id      // identifier of the property
   );

Abbiamo anche usato le funzioni delle proprietà del simbolo SymbolInfoDouble() e SymbolInfoString() per ottenere rispettivamente la dimensione del contratto e la valuta di base per il simbolo corrente (coppia di valute). La funzione SymbolInfoDouble() prende il nome del simbolo e un ENUM_SYMBOL_INFO_DOUBLE identificatore come parametri mentre la funzione SymbolInfoString() accetta il nome del simbolo e un identificatore ENUM_SYMBOL_INFO_STRING come parametri. I risultati di queste funzioni vengono archiviati nelle variabili dichiarate per ogni tipo di dati.

double  SymbolInfoDouble(
   string  name,        // symbol
   int     prop_id      // identifier of the property
   );

Il calcolo che abbiamo fatto qui è molto semplice.

Per ottenere il margine richiesto per piazzare un trade, consideriamo due situazioni:

  1. La valuta di base è USD (USD/CAD, USD/CHF, USD/JPY, ecc.)

Margine richiesto = Dimensione del contratto per lotto/Leva

     2.  La valuta di base non è USD (EUR/USD, ecc.)

Margine richiesto = prezzo corrente del simbolo * dimensione del contratto per lotto/Leva.

Ora decidiamo di verificare se il margine richiesto per fare trading sulla dimensione del lotto specificata o sul volume è maggiore della percentuale di margine libero che si desidera utilizzare per un del trade. Se il margine richiesto è inferiore, la funzione restituisce TRUE e il trade è piazzato, altrimenti restituisce FALSE e il trade non sarà piazzato.

La funzione getBuffers:

//+-----------------------------------------------------------------------+
// OUR PUBLIC FUNCTIONS                                                   |
//+-----------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| DOINIT FUNCTION
//| *Takes the ADX indicator's Period and Moving Average indicator's 
//| period as input parameters 
//| *To be used in the OnInit() function of our EA                                                               
//+------------------------------------------------------------------+
void MyExpert::doInit(int adx_period,int ma_period)
  {
//--- Get handle for ADX indicator
   ADX_handle=iADX(symbol,period,adx_period);
//--- Get the handle for Moving Average indicator
   MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE);
//--- What if handle returns Invalid Handle
   if(ADX_handle<0 || MA_handle<0)
     {
      Errormsg="Error Creating Handles for indicators";
      Errcode=GetLastError();
      showError(Errormsg,Errcode);
     }
// Set Arrays as series
// the ADX values arrays
   ArraySetAsSeries(ADX_val,true);
// the +DI value arrays
   ArraySetAsSeries(plus_DI,true);
// the -DI value arrays
   ArraySetAsSeries(minus_DI,true);
// the MA values arrays
   ArraySetAsSeries(MA_val,true);
  }

Questa è una funzione pubblica che intendiamo utilizzare nella funzione OnInit()del nostro EA che scriveremo presto e farà due cose.

Innanzitutto, imposterà gli handle per i nostri indicatori ed eseguirà anche l'azione array-set-as-series sulle variabili array. Ha due parametri di input che verranno forniti all'interno del nostro codice EA.

La funzione getBuffers:

//+------------------------------------------------------------------+
//|  DOUNINIT FUNCTION
//|  *No input parameters
//|  *Used to release ADX and MA indicators handleS                                                                |
//+------------------------------------------------------------------+
void MyExpert::doUninit()
  {
//--- Release our indicator handles
   IndicatorRelease(ADX_handle);
   IndicatorRelease(MA_handle);
  }
  

Questa funzione è anche una funzione membro pubblica che verrà utilizzata nella funzione UnDeInitdel nostro EA per rilasciare tutti gli handle per gli indicatori che abbiamo utilizzato. Questo non ha parametri di input.

La funzione getBuffers:

//+------------------------------------------------------------------+
//| CHECKBUY FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Buy setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Buy conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkBuy()
  {
/*
    Check for a Long/Buy Setup : MA increasing upwards, 
    previous price close above MA, ADX > ADX min, +DI > -DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Buy Conditions
   bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards
   bool Buy_Condition_2=(Closeprice>MA_val[1]);         // previous price closed above MA
   bool Buy_Condition_3=(ADX_val[0]>ADX_min);          // Current ADX value greater than minimum ADX value
   bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]);       // +DI greater than -DI
//--- Putting all together   
   if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

  

Questa funzione verrà utilizzata per verificare se una condizione di acquisto è stata impostata o meno. Ecco perché il suo tipo restituito è bool. Significa che restituirà un TRUE o un FALSE. È qui che abbiamo definito la nostra strategia di trading. Se viene soddisfatta una condizione di acquisto in base alla strategia che abbiamo definito, restituirà TRUE; tuttavia, se la condizione di acquisto non viene soddisfatta, verrà restituito FALSE. Quando si utilizza questa funzione nel nostro codice, effettueremo un acquisto se restituisce TRUE.

La prima cosa che abbiamo fatto qui è chiamare la funzione membro interna getBuffers(), che copierà tutti i valori dell'array necessari alla funzione checkBuy nelle corrispondenti variabili dell'array.

Le condizioni qui codificate sono state spiegate nel primo articolo.

La funzione getBuffers:

//+------------------------------------------------------------------+
//| CHECKSELL FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Sell setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Sell conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkSell()
  {
/*
    Check for a Short/Sell Setup : MA decreasing downwards, 
    previous price close below MA, ADX > ADX min, -DI > +DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Sell Conditions
   bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]);  // MA decreasing downwards
   bool Sell_Condition_2=(Closeprice <MA_val[1]);                         // Previous price closed below MA
   bool Sell_Condition_3=(ADX_val[0]>ADX_min);                            // Current ADX value greater than minimum ADX
   bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]);                        // -DI greater than +DI

//--- Putting all together
   if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

Proprio come checkBuy, questa funzione verrà utilizzata per verificare se una condizione di vendita è stata impostata o meno. Ecco perché anche il suo tipo restituito è bool. Significa che restituirà un TRUE o un FALSE.  Qui è dove abbiamo definito la nostra strategia di trading di Vendita. Se una condizione di vendita è soddisfatta in base alla strategia che abbiamo definito, restituirà TRUE; tuttavia, se la condizione di vendita non è soddisfatta restituirà FALSE.

Quando si utilizza questa funzione nel nostro codice, effettueremo quindi una vendita se restituisce TRUE. Proprio come in checkBuy, abbiamo chiamato prima la funzione interna getBuffers(). La condizioni qui codificate sono state spiegate anche nel primo articolo.

La funzione getBuffers:

//+------------------------------------------------------------------+
//| OPENBUY FUNCTION
//| *Has Input parameters - order type, Current ASK price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=askprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Buy order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=askprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Buy order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

Questa è la funzione che apre una posizione di acquisto ogni volta che viene chiamata nel nostro EA. Ha come parametri di input la maggior parte delle variabili che serviranno per piazzare il trade; e alcune delle variabili saranno fornite dal nostro codice EA. Noterai, come spiegato nel primo articolo, che qui abbiamo utilizzato le variabili di tipo MqlTraderequest.

Non avremo bisogno di usarli nel nostro codice EA. Prima che un trade venga piazzato, vogliamo confermare se l'utente vuole controllare il margine, se il il valore di Chk_Margin (che sarà ottenuto dall'EA) è 1, quindi chiamiamo la funzione MarginOK() che lo farà per noi. Il risultato di questa funzione determina il prossimo passo da compiere.  Tuttavia, se l'utente non vuole controllare il margine, allora continuiamo e piazzare il trade.

La funzione getBuffers:

//+------------------------------------------------------------------+
//| OPENSELL FUNCTION
//| *Has Input parameters - order type, Current BID price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=bidprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Sell order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=bidprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Sell order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

Proprio come la funzione openBuy, questa funzione apre una posizione di vendita ogni volta che viene chiamata nel nostro EA. Ha come parametri di input la maggior parte delle variabili che serviranno per piazzare il trade; e alcune delle variabili saranno fornite dal nostro codice EA.

Proprio come abbiamo fatto quando abbiamo aperto una posizione di acquisto prima che venga piazzato un trade, vogliamo confermare se l'utente vuole controllare il margine, se il valore di Chk_Margin (che sarà ottenuto dall'EA) è 1, allora chiamiamo la funzione MarginOK () che lo farà per noi.

Il risultato di questa funzione determina il prossimo passo da compiere. Tuttavia, se l'utente non vuole controllare il margine, allora continuiamo e piazziamo il trade.

Ora abbiamo terminato la dichiarazione e la definizione della nostra classe e delle funzioni membro, tuttavia, abbiamo tralasciato alcuni altri compiti che intendiamo gestire nel nostro codice EA. Questi includono il controllo delle barre disponibili, il controllo delle nuove barre e il controllo delle posizioni aperte disponibili. Saranno gestiti nel nostro codice EA.

Per vedere un elenco di tutte le funzioni e metodi della nostra classe, fare clic sulle funzioni comando/menu sul MetaEditor come mostrato di seguito. La funzione visualizza tutte le funzioni membro incluso il Destructor che non abbiamo dichiarato esplicitamente nel nostro codice.

I membri protetti sono indicati da frecce verdi mentre il Constructor e il destructor sono indicati da frecce blu.

 

Funzioni membro di classe

Figura 3. e Il nostro membro di classe funziona mostrando il destructor di classe

Allora, cosa viene dopo?

Ti ho sentito dire, debug? Forse hai ragione. È sempre bene testare e vedere se il tuo codice contiene errori altrimenti rimarrai deluso quando lo rilasci al pubblico. Il problema qui è che questo è solo un file di inclusione, non è un codice o uno script di consulente esperto o un codice indicatore che può essere allegato al grafico. A questo punto hai due opzioni (dalla mia esperienza),

  • o rischi di premere il pulsante debug sul tuo editor in modo che il debugger riporti qualsiasi errore nel tuo codice ad eccezione di un errore "nessun file eseguibile prodotto", che verrà visualizzato poiché un file .mqh non può essere compilato in un .ex5 file.  OPPURE
  • Vai avanti e scrivi il codice per l'EA che utilizzerà la tua classe. Una volta avviato il debug di EA, il file incluso verrà controllato insieme ad esso. In effetti, questo è il modo migliore e più accettabile per farlo.

Figura 4. I file .mqh non possono essere compilati

2.2. SCRIVERE L’EXPERT ADVISOR

Immagino che il tuo editor sia ancora aperto. Ricomincia un nuovo documento ma questa volta seleziona Expert Advisor. (Si prega di consultare il primo articolo per i dettagli). Ma questa volta, chiama il tuo EA 'my_oop_ea' .

Questa è dove dovresti essere ora:


Ora siamo pronti a scrivere il nostro EA basato su OOP.

La prima cosa che faremo qui è includere la classe che abbiamo appena scritto usando il comando del preprocessore #include. Includi solo la classe immediatamente dopo l'ultimo comando di proprietà del preprocessore

//+------------------------------------------------------------------+
//|                                                    my_oop_ea.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
// Include  our class
#include <my_expert_class.mqh>

In esso ci sono due modi per includere un file,

// Include  using angle brackets
#include <my_expert_class.mqh>
// Include  using quotations
#include "my_expert_class.mqh"

Quando usiamo la parentesi angolare (< ... >), significa che il file da includere verrà preso dalla directory include standard (ovvero la cartella include all'interno della directory MQL5).  La directory corrente (che è la cartella Experts all'interno della directory MQL5 non sarà considerata come un possibile luogo in cui cercare il file). Tuttavia, se il file è racchiuso tra virgolette (" ... "), il file verrà considerato nella directory corrente (che è la cartella Experts) e la directory standard (cartella Include) non sarà controllata.

Se la tua classe è salvata nella cartella Include (directory standard) e usi le virgolette invece delle parentesi angolari o viceversa, riceverai un errore durante la compilazione del codice.


Figura 5. Viene visualizzato un messaggio di errore quando non è possibile trovare il file di inclusione

EA PARAMETRI DI INPUT

//--- input parameters
input int      StopLoss=30;      // Stop Loss
input int      TakeProfit=100;   // Take Profit
input int      ADX_Period=14;    // ADX Period
input int      MA_Period=10;     // Moving Average Period
input int      EA_Magic=12345;   // EA Magic Number
input double   Adx_Min=22.0;     // Minimum ADX Value
input double   Lot=0.2;          // Lots to Trade
input int      Margin_Chk=0;     // Check Margin before placing trade(0=No, 1=Yes)
input double   Trd_percent=15.0; // Percentage of Free Margin To use for Trading

La maggior parte dei parametri di input qui non sono nuovi. Parliamo di quelli nuovi.

Abbiamo introdotto una variabile intera per contenere un valore di 1 se vogliamo usare il controllo del margine o 0 se non lo vogliamo. Abbiamo anche dichiarato un'altra variabile per contenere la percentuale massima di margine libero da utilizzare nell'apertura di una posizione. Questi valori verranno in seguito utilizzati nel nostro oggetto di classe quando vengono creati.

Subito dopo i parametri di input, definiamo altri due parametri (STP e TKP) che vogliamo essere in grado di manipolare (per far fronte a prezzi a 5 e 3 cifre) poiché non possiamo modificare i valori delle variabili di input. Quindi creiamo un oggetto della nostra classe da utilizzare all'interno del nostro codice EA.

//--- Other parameters
int STP,TKP;   // To be used for Stop Loss & Take Profit values
// Create an object of our class
MyExpert Cexpert;

Come spiegato in precedenza, per creare un oggetto di una classe, si utilizza il nome della classe seguito dal nome dell'oggetto che si desidera creare. Qui abbiamo creato un oggetto Cexpert che è un tipo di classe MyExpert. Cexpert ora può essere utilizzato per accedere a tutte le funzioni membro pubbliche della classe MyExpert.

EA SEZIONE INIZIALIZZAZIONE

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Run Initialize function
   Cexpert.doInit(ADX_Period,MA_Period);
//--- Set all other necessary variables for our class object
   Cexpert.setPeriod(_Period);     // sets the chart period/timeframe
   Cexpert.setSymbol(_Symbol);     // sets the chart symbol/currency-pair
   Cexpert.setMagic(EA_Magic);    // sets the Magic Number
   Cexpert.setadxmin(Adx_Min);    // sets the ADX miniumm value
   Cexpert.setLOTS(Lot);          // set the Lots value
   Cexpert.setchkMAG(Margin_Chk); // set the margin check variable
   Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade
//--- Let us handle brokers that offers 5 digit prices instead of 4
   STP = StopLoss;
   TKP = TakeProfit;
   if(_Digits==5 || _Digits==3)
     {
      STP = STP*10;
      TKP = TKP*10;
     }  
//---
   return(0);
  }

A questo punto, abbiamo chiamato la funzione doInit della nostra classe e le abbiamo passato le variabili di periodo ADX e MA. Successivamente impostiamo tutte le altre variabili che saranno necessarie all'oggetto che abbiamo appena creato in modo che venga memorizzato nelle variabili membro dell'oggetto utilizzando le funzioni che abbiamo già descritto durante la scrittura della nostra classe.

La prossima riga di codici non dovrebbe essere strana, decidiamo semplicemente di regolare i nostri valori di Stop Loss e Take Profit per i prezzi a tre e cinque cifre.

EA SEZIONE REINIZIALIZZAZIONE

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Run UnIntilialize function
   Cexpert.doUninit();
  }

Chiamiamo la funzione doUninit della classe in modo da rilasciare tutti gli handle degli indicatori che devono essere stati creati nella funzione di inizializzazione EA.

EA SEZIONE ONTICK

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Do we have enough bars to work with
   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) // if total bars is less than 60 bars
     {
      Alert("We have less than 60 bars, EA will now exit!!");
      return;
     }

//--- Define some MQL5 Structures we will use for our trade
   MqlTick latest_price;      // To be used for getting recent/latest price quotes
   MqlRates mrate[];          // To be used to store the prices, volumes and spread of each bar
/*
     Let's make sure our arrays values for the Rates
     is store serially similar to the timeseries array
*/
// the rates arrays
   ArraySetAsSeries(mrate,true);

La prima cosa che facciamo qui è controllare il totale delle barre disponibili. Se è sufficiente per il nostro EA per fare trading, altrimenti non verrà fatto trading finché non avremo abbastanza barre (cioè 60 barre). Poi abbiamo dichiarato due variabili della struttura MQL5 (MqlTick e MqlRates). Infine, utilizziamo la funzione ArraySetAsSeries sull'array dei tassi.

//--- Get the last price quote using the MQL5 MqlTick Structure
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("Error getting the latest price quote - error:",GetLastError(),"!!");
      return;
     }

//--- Get the details of the latest 3 bars
   if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
     {
      Alert("Error copying rates/history data - error:",GetLastError(),"!!");
      return;
     }

//--- EA should only check for new trade if we have a new bar
// lets declare a static datetime variable
   static datetime Prev_time;
// lest get the start time for the current bar (Bar 0)
   datetime Bar_time[1];
// copy time
   Bar_time[0] = mrate[0].time;
// We don't have a new bar when both times are the same
   if(Prev_time==Bar_time[0])
     {
      return;
     }
//copy time to static value, save
   Prev_time = Bar_time[0]; 
   

Qui, abbiamo usato la funzione SymbolInfoTick per ottenere l'ultima quotazione del prezzo e abbiamo usato CopyRates per ottenere le ultime tariffe per le ultime tre barre (barra attuale inclusa). Le prossime righe di codice controllano se abbiamo una nuova barra. Abbiamo dichiarato due variabili datetime, una è una variabile statica (Prev_Time) e l'altra è Bar_Time.

Se abbiamo una nuova barra, il tempo della barra viene memorizzato nella variabile statica Prev_Time in modo che possiamo confrontare il suo valore con il valore di Bar_Time nel tick successivo. Nel segno di spunta successivo, se Prev_Time è uguale a Bar_Time, allora è sempre la stessa barra di cui è stata memorizzata l'ora. Quindi il nostro EA si riposerà.

Se tuttavia Bar_Time non è uguale a Prev_Time, allora abbiamo una nuova barra. Decidiamo di memorizzare l'ora di inizio della nuova barra nella variabile datetime statica, Prev_Time e il nostro EA può ora procedere alla verifica della presenza di nuove opportunità di ACQUISTO o VENDITA.

//--- we have no errors, so continue
//--- Do we have positions opened already?
    bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position
    
    if (PositionSelect(_Symbol) ==true)  // we have an opened position
    {
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            Buy_opened = true;  //It is a Buy
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            Sell_opened = true; // It is a Sell
         }
    }

Decidiamo di verificare se abbiamo già una posizione aperta. Vogliamo solo essere sicuri di aver aperto un trade di acquisto quando non è stato aperto alcun acquisto e un trade di vendita quando non c'è una vendita aperta.

// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1
   Cexpert.setCloseprice(mrate[1].close);  // bar 1 close price
//--- Check for Buy position
   if(Cexpert.checkBuy()==true)
     {
      // Do we already have an opened buy position
      if(Buy_opened)
        {
         Alert("We already have a Buy Position!!!");
         return;    // Don't open a new Buy Position
        }
      double aprice = NormalizeDouble(latest_price.ask,_Digits);              // current Ask price
      double stl    = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss
      double tkp    = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit
      int    mdev   = 100;                                                    // Maximum deviation
      // place order
      Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev);
     }

Ora siamo tornati all'oggetto che abbiamo creato, perché? Perché siamo stati in grado di effettuare tutti i controlli necessari affinché il nostro oggetto svolga il suo lavoro.

La prima cosa che facciamo è ottenere il prezzo di chiusura per la barra precedente utilizzando la funzione membro setCloseprice degli oggetti.

Quindi chiamiamo la funzione checkBuy per scoprire se una condizione per l’acquisto è impostata, se restituisce TRUE, allora vogliamo essere sicuri di non avere una posizione di acquisto già aperta. Se non abbiamo una posizione di acquisto già aperta, allora prepariamo le variabili richieste da utilizzare per il nostro ordine (il tipo di ordine, il prezzo ASK corrente, stop perdita, take profit e massima deviazione) e chiamare la funzione openBuy. Guarda com'è facile da usare la classe che abbiamo scritto. 

//--- Check for any Sell position
   if(Cexpert.checkSell()==true)
     {
      // Do we already have an opened Sell position
      if(Sell_opened)
        {
         Alert("We already have a Sell position!!!");
         return;    // Don't open a new Sell Position
        }
      double bprice=NormalizeDouble(latest_price.bid,_Digits);                 // Current Bid price
      double bstl    = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss
      double btkp    = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit
      int    bdev=100;                                                         // Maximum deviation
      // place order
      Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev);
     }

Questo è lo stesso di quello che abbiamo fatto sopra. Poiché stiamo verificando una vendita, abbiamo chiamato la funzione checkSell e se restituisce TRUE e non abbiamo già una posizione di vendita aperta, prepariamo le variabili richieste per effettuare il nostro ordine (il tipo di ordine, il prezzo ASK corrente, stop perdita, take profit e deviazione massima) e quindi chiamare la funzione openSell.

Abbastanza facile, non è vero? Abbiamo finito di scrivere i codici. Ora è il momento di eseguire il debug del nostro codice. Se non sai come usare il debugger, per favore leggi il primo articolo per capirne meglio.

Quando premi F5 o il pulsante debug, il file incluso (la nostra classe) sarà incluso e verificato, e se c'è qualche errore, lo segnalerà. Una volta visualizzato l'errore, devi tornare al codice e correggere l'errore.

Figura 6. Il nostro file di inclusione è incluso durante il debug del codice EA principale

Se tutto va bene, hai fatto un buon lavoro. È giunto il momento di testare il nostro EA utilizzando lo strategy tester. Dobbiamo compilare il nostro EA prima di testarlo con lo Strategy Tester. Per fare ciò, fai clic sul pulsante Compileo premi F7 sulla tastiera del tuo computer.

Figura 7. Fare clic sul pulsante del menu Compile per compilare il nostro codice

Dalla barra dei menu del terminale di trading, vai su Visualizza -> Strategy Tester o premi CTRL+R per avviare lo strategy tester. (Per i dettagli su come utilizzare il tester, leggere il primo articolo).

Per poter testare l'EA con lo strategy tester, devi prima di tutto compilarlo. Se non lo compili, riceverai un errore quando selezioni Expert Advisor nella barra delle impostazioni dello strategy tester. (Ho appena scoperto questo nella nuova versione del terminale.)

Figura 8. Il codice di EA dovrebbe essere compilato prima del suo utilizzo nello Strategy Tester

Di seguito sono riportati i risultati dello Strategy tester per il nostro Expert Advisor basato su OOP.

 Figura 9. I risultati di trading per il nostro Expert Advisor Orientato agli Oggetti

Il Grafico:

Figura 10. I risultati del grafico per il nostro Expert Advisor Orientato agli Oggetti

Il report/journal sull'attività di trading:


Figura 11. I risultati dell'attività di trading per il nostro Expert Advisor e Orientato agli Oggetti


Il grafico per il test:

Figura 12. I risultati del grafico di trading per il nostro Expert Advisor Orientato agli Oggetti

Conclusione

In questo articolo abbiamo discusso, ad un certo livello, le basi di una classe e come usarla per scrivere un semplice Expert Advisor. Non abbiamo approfondito troppo le aree avanzate delle classi, ma ciò che abbiamo discusso in questo articolo è sufficiente per aiutarti a sviluppare te stesso fino a un livello in cui sarai in grado di scrivere il tuo codice Expert Advisor orientato agli oggetti. 

Abbiamo anche discusso su come possiamo verificare il margine libero in modo tale che il nostro EA non effettui trade quando il margine libero disponibile non è sufficiente per la posizione che vogliamo aprire.

Ora sarai d'accordo con me che il nuovo linguaggio MQL5 ha molto di più da offrire e non devi essere un guru della programmazione per trarre vantaggio da questo nuovo linguaggio. Questo è il motivo principale per scrivere le guide passo-passo.


Tradotto dall’inglese da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/en/articles/116

File allegati |
my_expert_class.mqh (17.52 KB)
my_oop_ea.mq5 (6.77 KB)
Il Metodo Ottimale per il Calcolo del Volume Totale della Posizione in Base al Numero Magico Specificato Il Metodo Ottimale per il Calcolo del Volume Totale della Posizione in Base al Numero Magico Specificato
Il problema del calcolo del volume totale della posizione del simbolo specificato e del numero magico è considerato in questo articolo. Il metodo proposto richiede solo la parte minima necessaria della cronologia degli affari, trova il momento più vicino in cui la posizione totale era uguale a zero ed esegue i calcoli con le operazioni recenti. Viene anche considerato il lavoro con le variabili globali del terminale client.
Una soluzione senza DLL per comunicare tra i terminali MetaTrader 5 utilizzando le Named Pipe Una soluzione senza DLL per comunicare tra i terminali MetaTrader 5 utilizzando le Named Pipe
L'articolo descrive come implementare la comunicazione tra processi tra i terminali client MetaTrader 5 utilizzando le named pipe. Per l'utilizzo delle named pipe, viene sviluppata la classe CNamedPipes. Per il test del suo utilizzo e per misurare il throughput della connessione, vengono presentati l'indicatore di tick, gli script server e client. L'uso di named pipe è sufficiente per le quotazioni in tempo reale.
Utilizzo della Funzione TesterWithdrawal() per il Modeling dei Prelievi di Profitto Utilizzo della Funzione TesterWithdrawal() per il Modeling dei Prelievi di Profitto
Questo articolo descrive l'uso della funzione TesterWithDrawal() per stimare i rischi nei sistemi commerciali che implicano il ritiro di una certa parte delle attività durante il loro funzionamento. Inoltre, descrive l'effetto di questa funzione sull'algoritmo di calcolo del drawdown del capitale nello Strategy Tester. Questa funzione è utile quando si ottimizzano i parametri dei propri Expert Advisor.
Una Libreria per la Costruzione di un Grafico tramite l'API Google Chart Una Libreria per la Costruzione di un Grafico tramite l'API Google Chart
La costruzione di vari tipi di diagrammi è una parte essenziale nelle analisi della situazione del mercato e del test di un sistema di trading. Spesso, per costruire un diagramma di bell'aspetto, è necessario organizzare l'output dei dati in un file, dopo di che viene utilizzato in applicazioni come MS Excel. Questo non è molto conveniente e ci priva della possibilità di aggiornare dinamicamente i dati. L'API di Google Charts ha fornito i mezzi per creare grafici in modalità online, inviando una richiesta speciale al server. In questo articolo cercheremo di automatizzare il processo di creazione di tale richiesta e ottenere un grafico dal server di Google.