Expert advisor

impariamo a creare il nostro primo expert advisor per metatrader

Questo articolo si pone come guida per la creazione di un expert advisor passando in rassegna le funzioni principali offerte da mql (apertura e chiusura di ordini, trailing stop, stop-loss, target ecc... ). Prenderemo come esempio l'ea MACD Sample il cui codice è disponibile di default con il download della piattaforma metatrader. Potete consultare l'articolo da cui ho preso spunto visitando questo link:

Articles MQL4 - MACD Sample EA

Andiamo ad analizzare per prima cosa la strategia di trading che sta alla base del MACD Sample:

  • - il sistema aprirà e gestirà una sola operazione alla volta
  • - entrata LONG se l'indicatore MACD è sotto lo zero, gira verso l'alto ed incrocia la linea rossa che sta scendendo

MACD long signal

  • - entrata SHORT se il MACD è sopra lo zero, sta scendendo ed incrocia con la linea rossa che va nella direzione opposta

MACD short signal

  • - uscita dal long se viene raggiunto il target prefissato, viene raggiunto lo stop (utilizzermo il trailing stop) o se viene generato un segnale di short
  • - uscita dallo short se viene raggiunto il target prefissato, viene raggiunto lo stop (utilizzermo il trailing stop) o se viene generato un segnale di long

Iniziamo a mettere giù l'ea. In alcuni casi il codice da scrivere può essere davvero lungo, avere una traccia da seguire può essere utile a non perder il filo:

  • 1. Inizializzare variabili
  • 2. Controlli iniziali
    •  - controllo grafico, numero di barre sul grafico
    •  - controllo sui valori delle variabili esterne: lotti, stop-loss, target point, trailing stop
  • 3. Impostare le variabili interne per un accesso più rapido
  • 4. Controllo sul conto:
    •  - controllo sulla disponibilità di fondi
    •  - possibilità di long? se si apri posizione ed esci
    •  - possibilità di short? se si apri posizione ed esci
  • 5. Controllo delle posiozioni precedentemente aperte
    •  - se è long controlla se deve essere chiusa o se spostare lo stop
    •  - se è short controlla se deve essere chiusa o se spostare lo stop

Andiamo ora ad analizzare passo-passo ogni singolo step:

1. Inizializzazione delle variabili:

extern double TakeProfit = 50;
extern double Lots = 0.1;
extern double TrailingStop = 30;
extern double MACDOpenLevel=3;
extern double MACDCloseLevel=2;
extern double MATrendPeriod=26;

Le variabili esterne permettono una notevole flessibilità al codice mql, esse infatti possono essere modificate anche a run-time nelle proprietà dell'expert advisor. Se non vengono modificate assumono invece il valore impostato di default nel codice. Capite quindi l'importanza di dare dei valori molto significativi a queste varibili in modo da poter far comprendere la loro utilità anche a chi non conosce una riga di codice.

2. Controlli inziali

if(Bars < 100) {
 Print("bars less than 100");
 return(0);
}
if(TakeProfit < 10) {
 Print("TakeProfit less than 10");
 return(0); // check TakeProfit
}

Questi controlli possono sembrare banali ma possono aiutare a non cadere in errore. Il Bars < 100 è utile per sapere che si sta lavorando su un grafico normale, il TakeProfit < 10 serve per controllare che i valori delle variabili esterne rimangano coerenti con la logica del programma.

3. Impostare le variabili interne per un accesso più rapido e una maggiore comodità:

int start() {
double MacdCurrent, MacdPrevious, SignalCurrent;
double SignalPrevious, MaCurrent, MaPrevious;
int cnt, ticket, total;

MacdCurrent = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, 0);
MacdPrevious = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_MAIN, 1);
SignalCurrent = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, 0);
SignalPrevious = iMACD(NULL, 0, 12, 26, 9, PRICE_CLOSE, MODE_SIGNAL, 1);
MaCurrent = iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, 0);
MaPrevious = iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, 1);

Anche qui come per le variabili esterne un utilizzo intelligente dei nomi delle variabili può migliorare la leggibilità del codice. Per le funzioni iMACD e iMA vi rimando invece al topic:

Funzioni iMACD e iMA in metatrader

Faccio solo notare come nei parametri delle funzioni vengano utilizzate delle variabili esterne tipo MATrendPeriod.

Per quanto riguarda le variabili interne prenso come esempio la variabile MaPrevious. Deve essere stata dichiarata dello stesso tipo del valore che la funzione iMA restituisce. Dopo averla inizializzata in questa maniera scrivere MaPrevious oppure iMA(NULL, 0, MATrendPeriod, 0, MODE_EMA, PRICE_CLOSE, 1) sarà equivalente dal punto di vista del valore restituito.

4. Controllo sul conto:

Abbiamo detto che il nostro ea aprirà una sola posizione per volta, prima di verificare quindi che ci sia o meno la possibilità di piazzare un'ordine dobbiamo controllare che non ci siano delle posizioni ancora aperte:

total = OrdersTotal();
if(total < 1) {

La funzione OrdersTotal() restituisce il numero di ordini aperti o pendenti, il controllo total < 1 permette quindi saltare il controllo sulla possibilità di aprire posizioni nel caso ci sia già almeno un ordine aperto.

5. Controllo sulla disponibilità di fondi:

if(AccountFreeMargin() < (1000 * Lots)) {
 Print("We have no money. Free Margin = ", AccountFreeMargin());
 return(0);
}

La funzione AccountFreeMargin() restituisce il margine che si ha a disposizione sul proprio conto. Il controllo AccountFreeMargin() < (1000 * Lots) serve per non far aprire ordini spropositati e fare un minimo di money management. Ricordo che la variabile Lots è una variabile esterna modificabile a run-time.

E' possibile aprire un long?

// check for long position (BUY) possibility
if(MacdCurrent < 0 && MacdCurrent > SignalCurrent && MacdPrevious < SignalPrevious && MathAbs(MacdCurrent) > (MACDOpenLevel * Point) && MaCurrent > MaPrevious){

 ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green);

 if(ticket>0){
  if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES)) Print("BUY order opened : ",OrderOpenPrice());

 }
 else Print("Error opening BUY order : ",GetLastError());
  return(0);
}

Abbiamo già descritto le condizioni per entrare con un long, queste vengono esaurite nel controllo effettuato dall'if.

La funzione OrderSend() è la funzione più utilizzata per aprire una posizione o un'ordine pendente, ecco la sua dichiarazione:

int OrderSend(
 string symbol,
 int cmd,
 double volume,
 double price,
 int slippage,
 double stoploss,
 double takeprofit,
 string comment=NULL,
 int magic=0,
 datetime expiration=0,
 color arrow_color=CLR_NONE
)

La funzione ritorna un numero intero che corrisponde al numero del ticket relativo all'ordine aperto in caso di successo mentre ritorna -1 in caso di insuccesso.

Il primo parametro symbol corrisponde ovviamente al cross su cui aprire la posizione, accetta in ingresso delle stringhe quali: "EURUSD", "USDJPY" ecc. Per aprirlo invece sul grafico a cui l'ea è attaccato basterà richiamare la funzione Symbol().

Il secondo parametro cmd è molto importante perchè permette di scegliere il tipo di ordine che si vuole aprire, questa tabella riassuntiva illustra tutti i vari valori possibili:

Constant Value Description
OP_BUY 0 Buying position.
OP_SELL 1 Selling position.
OP_BUYLIMIT 2 Buy limit pending position.
OP_SELLLIMIT 3 Sell limit pending position.
OP_BUYSTOP 4 Buy stop pending position.
OP_SELLSTOP 5 Sell stop pending position.

Il terzo parametro volume corrisponde al numero di lotti con cui si vuole aprire la posizione. Ricordo che Lots è stata inizializzata come variabile esterna quindi è possibile modificarla anche a run-time. Effettuare un controllo su questa variabile può quindi rivelarsi utile, è importante sapere che per la maggior parte dei broker il valore minimo per aprire un lotto è 0.1 (minilotto), alcuni accettano anche 0.01 (microlotti); per scoprirlo è sufficente stampare il valore restituito dalla seguente chiamata MarketInfo(Symbol(), MODE_MINLOT). Per quanto riguarda il valore massimo è possibile utilizzare il valore restituito dala funzione MarketInfo(Symbol(), MODE_MAXLOT).

Il quarto parametro corrisponde al prezzo in cui si intende aprire la posizione, è un parametro su cui è necessario spendere qualche parola:

Nel caso in cui si intenda aprire uno short è necessario impostarlo a Bid, nel caso di un long ad Ask. Come spesso succede però la posizione non viene mai aperta istantaneamente è per questo che si può ricorrere al quinto parametro slippage.

Lo slippage è il valore espresso in pips della massima differenza accettabile perchè l'ordine venga aperto. Un esempio chiarirà meglio la cosa:

vogliamo aprire un long sull'euro, l'Ask è a 1.4750. Dal momento in cui richiamiamo OrderSend() al momento in cui viene aperta la posizione e l'Ask varia. Se abbiamo impostato il parametro slippage a 3 una variazione maggiore di 3 pips farà in modo che l'ordine non venga aperto cioè se il prezzo sarà inferiore o ugule a 1.4753 l'ordine verrà eseguito altrimenti no. La cosa sia valida anche nel caso che il prezzo sia inferiore (e quindi un punto di entrata long migliore) per esempio 1.4746. A rigor di logica la posizione non viene aperta anche se si tratta di un punto migliore d'ingresso.

Se l'ordine deve essere eseguito su un grafico differente da quello in cui l'ea è attaccato è possibile utilizzare la funzione MarketInfo() descritta in questo topic:

Funzione MarketiInfo()

I parametri takeprofit e stoploss si commentano da soli. In alcuni broker esistono delle limitazioni anche in questo e non è possibile impostare stop o target troppo stretti. Per saperlo utilizzate la funzione MarketInfo(Symbol(), MODE_STOPLEVEL).

Il parametro comment è una stringa che viene utilizzata per rendere più leggibile l'apertura degli ordini e sapere se un'ordine è stato aperto dall'ea, di solito infatti viene valorizzata con il nome dell'ea.

Magic è un parametro che, come comment, viene utilizzato per identificare l' ea che ha aperto l'ordine ma è molto più importante. Ne capirete l'utilità quando cercherete di applicare più di un'expert advisor sullo stesso conto...come fare per sapere se l'ea deve chiudere un'ordine oppure non deve toccarlo perchè è stato aperto da un altro ea? Il magic id serve proprio come chiave univoca per risolvere eventuali problemi di questo tipo.

Expiration è di tipo datetime ed è utilizzato per sapere fino a quando dovranno rimanere attivi gli ordini pendenti.

L'ultimo parametro arrow_color è opzionale e viene valorizzato con il colore che si vuole utilizzare per disegnare una freccia nel grafico quando viene aperto l'ordine.

La funzione OrderSend() restituisce -1 in caso l'ordine non venga aperto, ecco il perchè del controllo ticket>0.

Per conoscere la causa d'errore basta stampare la stringa restituita dalla funzione GetLastError(). In caso l'errore non sia abbastanza esplicativo potete ricercare nel metaeditor maggiori dettagli.

Andiamo ad analizzare ora la funzione OrderSelect():

bool OrderSelect( int index, int select, int pool=MODE_TRADES)

La funzione OrderSelect() seleziona un ordine che può essere processato in seguito. La funzione restituisce true se l'ordine selezionato è presente, false in caso non lo sia.

Esistono due modi per selezionare un ordine, il primo è quello di ricercarlo per numero di ticket, il secondo di selezionarlo in base al tipo di ordine. Anche in questo caso se si verificano degli errori si può richiamare la funzione GetLastError() per capire cos'è successo.

Andiamo ora a vedere i parametri della funzione, stavolta iniziamo dal secondo:

select può essere valorizzato con SELECT_BY_TICKET oppure SELECT_BY_POS.

Il primo caso è quello che stiamo utilizzando nella scrittura del nostro ea, selezionando per ticket il primo parametro index sarà il numero del ticket che precedentemente (il valore restituito da OrderSend()) ci siamo salvati in una variabile. In questo caso il terzo parametro pool non è necessario.

Per determinare con precisione di che tipo di ordine si tratti si possono utilizzare diverse funzioni quali OrderType(), OrderCloseTime(), ecc. il cui funzionamento è abbastanza intuitivo.

Se invece impostiamo il secondo parametro a SELECT_BY_POS il primo parametro index verrà utilizzato per scorrere i vari ordini. Possiamo quindi scorrere tutti gli ordini variando l'indice (1, 2, 3...) finchè non restituirà false. In questo caso assume molta importanza l'utilizzo della funzione OrderTicket( ) per individuare univocamente l'ordine selezionato con la funzione OrderSelect().

L'ultimo parametro pool può essere valorizzato con MODE_TRADES (valore di default) oppure MODE_HISTORY. MODE_TRADES permette di selezionare gli ordini tra quelli attivi o pendenti, MODE_HISTORY tra quelli chiusi o cancellati.

Fin'ora abbiamo analizzato la porzione di codice relativa all'entrata long, la stessa cosa anche se opposta è valida per l'entrata short, andiamo solo ad analizzare la condizione d'entrata che prima avevamo tralasciato:

if(MacdCurrent > 0 && MacdCurrent < SignalCurrent && MacdPrevious > SignalPrevious && MacdCurrent > (MACDOpenLevel*Point) && MaCurrent < MaPrevious)

Le condizioni sono riassunte da questa frase:

entrata SHORT se il MACD è sopra lo zero, sta scendendo ed incrocia con la linea rossa che va nella direzione opposta.

Avrete notato che nelle condizioni di apertura non si spiega il controllo MacdCurrent > (MACDOpenLevel*Point), questo viene introdotto per fare in modo che non vengano generati troppi falsi segnali:

per escludere cambiamenti insignificanti e quindi ridurre il numero di falsi segnali viene aggiunto un ulteriore controllo: la variazione del MACD dovrà essere di almeno 5 pips.

Il controllo sull'apertura di ordini long e short è terminato ora passiamo alla parte di codice che verifica se è il caso di chiudere gli ordini o modificarli (spostare lo stop).

Per prima cosa vediamo se ci sono già degli ordini aperti:

total = OrdersTotal();
for(cnt = 0; cnt < total; cnt++)

Se esiste almeno un'ordine aperto entriamo nel ciclo for, il codice qui sotto serve ad individuare univocamente l'ordine utilizzando la funzione OrderSelect() con il parametro SELECT_BY_POS descritto in precedenza:

OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
if(OrderType() <= OP_SELL && // check for opened position
OrderSymbol()==Symbol()){ // check for symbol
 if(OrderType()==OP_BUY) // long position is opened
{

Nel caso sia stata indivuata la posizione long aperta si effettua un controllo sulle condizioni di chiusura dell'ordine:

// should it be closed?
if(MacdCurrent > 0 && MacdCurrent < SignalCurrent && MacdPrevious > SignalPrevious && MacdCurrent > (MACDCloseLevel * Point))

Anche nel caso della chiusura viene utilizzato il filtro per i falsi segnali: MacdCurrent > (MACDCloseLevel * Point).

Nel caso la condizione di chiusura sia verificata si procede alla chiusura dell'ordine:

OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
return(0); // exit

Faccio notare l'importanza del return(0) che comporta l'uscita immediata (ignorando il codice successivo) dalla funzione start e una nuova riesecuzione dall'inizio della stessa.

Ecco invece la dichiarazione di OrderClose():

bool OrderClose(
int ticket,
double lots,
double price,
int slippage,
color Color=CLR_NONE
)

La funzione restituisce true in caso di successo, false in caso contrario, anche in questo caso potete utilizzare GetLastError().

Il parametro ticket è lo stesso descritto in precedenza che viene restituito dalla funzione OrderSend().

Lots il numero di lotti che si vogliono chiudere, in tutti (credo) i broker è obbligatorio chiudere lo stesso numero di lotti aperti nella posizione.

Price è il valore in cui si intende chiudere, possiamo scegliere se chiuderlo in base al Bid o all'Ask, generlamente viene utilizzato Bid per i long Ask per gli short.

Per slippage vale lo stesso discorso fatto per la funzione OrderSend(), color è il colore opzionale che si intende tracciare nel grafico in corrispondenza della chiusura dell'ordine.

Ultimo punto da affrontare è il controllo sullo stop-loss, la strategia di spostare lo stop quando si è in guadagno con la posizione viene chiamata trailing stop:

if(TrailingStop > 0){
 if(Bid - OrderOpenPrice() > Point * TrailingStop){
  if(OrderStopLoss() < Bid - Point * TrailingStop){
   OrderModify(OrderTicket(), OrderOpenPrice(), Bid - Point * TrailingStop, OrderTakeProfit(), 0, Green);
   return(0);
  }
 }
}

E' da ricordare il fatto che le funzioni OrderOpenPrice(), OrderStopLoss() si riferiscono all'ordine precedentemente selezionato all'interno del ciclo for.

bool OrderModify(
int ticket,
double price,
double stoploss,
double takeprofit,
datetime expiration,
color arrow_color=CLR_NONE
)

OrderModify() serve a modificare l'ordine precedentemente aperto o pendente. I parametri sono abbastanza esplicativi:

ticket: chiave univoca per individuare l'ordine.
price: nuovo prezzo di apertura per un'ordine pendente.
stoploss: nuovo stop-loss, di fatto è il parametro che implementa il trailing stop.
takeprofit: nuovo target point.
expiration: nuova data di termine per l'ordine pendente.
arrow_color: colore dello stop o del target nel grafico.

Nel caso non si intendano modifcare dei parametri si ricorre all'utilizzo delle solite funzioni quali OrderOpenPrice(), OrderTakeProfit() ecc...

Gli stessi controlli sulla chiusura dell'ordine e sullo spostamento dello stop vengono effettuati nel caso di una posizione short, ecco il codice completo dell'expert advisor:

Codice completo dell'expert advisor: MACD_Sample.mq4