Un Expert Advisor per gestire semplici griglie

Expert Advisor per Griglie

Ciao! In questo articolo ti guiderò nella costruzione di un Expert Advisor che ti permetterà di aprire, chiudere e gestire numerosi ordini contemporaneamente.

Questo Expert Advisor è frutto di un esercizio di stile (non è comprovato che questa sia una strategia vincente), per imparare a gestire i cicli for e a ragionare sulle utilità che possono servirci nel trading.

La strategia deriva da un e-book che ho trovato su Amazon, di pessima fattura e senza arte né parte, che però mi ha dato modo di divertirmi con il codice per qualche mezz’ora.

L’ebook lo trovi qui –> PAGINA DELL’EBOOK

Ecco cosa troverai nell’articolo:

Progettiamo le caratteristiche dell’Expert Advisor

Andiamo veloci e ci scriviamo cosa deve fare il nostro Expert Advisor :

  • Deve aprire x ordini BUY STOP sopra il prezzo attuale, ad una distanza definita, mettiamo ogni 20 pips
  • Deve aprire x ordini SELL LIMIT sopra il prezzo attuale, ad una distanza definita, 20 pips ciascuno, ma partendo da 10 pips sopra il prezzo
  • Deve aprire x ordini SELL STOP sotto il prezzo attuale, ad una distanza definita, sempre i nostri 20 pips
  • Deve aprire x ordini BUY LIMIT sotto il prezzo attuale, ad una distanza definita, 20 pips ciascuno, ma partendo da 10 pips sotto il prezzo
  • Deve impostare uno Stop Loss e un Take Profit per ciascun ordine
  • Deve chiudere le posizioni che arrivano ad un profitto predeterminato
  • Deve essere facilmente governabile tramite pulsanti
  • Deve poter pulire il grafico dalle frecce e dalle linee tracciate dall’operatività

Detto ciò, sembra che dovremo scrivere decine di funzioni diverse, invece sarà molto semplice e con un massimo di 300 righe di codice faremo il necessario. 🙂

Iniziamo con il creare un nuovo file tramite la procedura guidata

Scegliamo “Expert Advisor” e clicchiamo “Avanti >”
Gli diamo un nome
E spuntiamo la funzione “OnChartEvent”, ci servirà per i pulsanti.
Lasciamo intatte queste opzioni, e clicchiamo “Fine”

Bene, abbiamo il nostro file pulito.

Definiamo il magic number, inserendo questa riga di codice sotto le righe #property

#define MAGIC 42

Così facendo, richiameremo il nostro magic number senza dover inserire una variabile. L’abbiamo, appunto, definito per tutto l’ambiente di lavoro.

Scriviamo il loop per attivare gli ordini

Allora. Sgranchisciti le dita perché ci prenderà qualche tempo.

Dovremo scrivere un’intera funzione, che verrà richiamata quando vogliamo far partire la griglia. Partiamo con il definirla: sarà una funzione di tipo void perché la sua esecuzione non darà output, ma elaborerà semplicemente delle sotto-funzioni. La scriviamo così:

void StartGrid()
{

}

Emozionante non è vero? Beh, no. Ahahahah. 😀

Dobbiamo conoscere innanzitutto quale sia il prezzo di partenza della nostra griglia, io utilizzo iClose, con indice [0], che corrisponde all’ultimo prezzo dell’ultima barra. Aggiungiamolo come variabile double:

void StartGrid()
{
   double price=iClose(_Symbol,PERIOD_CURRENT,0);
}

Prima ancora di inviare gli ordini pendenti, dovremo calcolare: i Lotti necessari, facendo attenzione che non vadano in conflitto con i minimi lotti consentiti dal broker e i massimi consentiti dal broker. Queste informazioni sono ottenibili con la funzione MarketInfo().

Utilizzeremo due semplici operatori ternari per sostituire due funzioni if–>
La “grammatica” del ternary operator vuole che ci sia un check di tipo booleano (vero o falso) e due diversi output, uno se il check è vero, l’altro se è falso. Sembra complesso ma non lo è, ecco come suona in italiano:

I lotti impostati in input sono maggiori dei massimi lotti consentiti?
Se vero, allora uso i massimi lotti consentiti, se falso, utilizzo i lotti in input.

Quindi, prima della funzione OnInit(), inseriamo la riga per l’input dei lotti, come variabile double:

input double INP_lots=0.01; // lotti griglia

E nella funzione StartGrid() inseriremo il check dei lotti, usando due ternary operators. Non ti senti un hacker?

 void StartGrid()
{
   double price=iClose(_Symbol,PERIOD_CURRENT,0);
   double maxlot=MarketInfo(_Symbol,MODE_MAXLOT);
   double minlot=MarketInfo(_Symbol,MODE_MINLOT);
   double lots;
   lots = (INP_lots>=maxlot) ? lots=maxlot : lots=INP_lots;
   lots = (INP_lots<=minlot) ? lots=minlot: lots=INP_lots;
} 

Ora saremo sicuri di non avere errori nell’OrderSend perchè confronteremo i lotti impostati dall’utente con i massimi e i minimi lotti.

Bene, ora che siamo sicuri di non avere (almeno) questo errore, procediamo con i BUY STOP e i SELL STOP.

Quanti ne dobbiamo attivare? Lo facciamo dire all’utente!
A quale distanza in punti? Lo facciamo dire all’utente!

Hai già capito, vero? Sopra l’OnInit() inseriamo due variabili int, una per il numero di ordini, l’altra per la distanza in punti:

input int DistanceOfGridOrders=100; // distanza in punti della griglia
input int MaxPendingOrders=5; // Numero massimo ordini (per lato, per tipo)

Con questo input, inseriremo ordini alternativamente STOP e LIMIT, in entrambi i sensi, a 10 pips (100 punti) di distanza l’uno dall’altro.

Manca qualcosa? Certo! Stop Loss e Take Profit! Altri due input di tipo integer, sempre contati in PUNTI:

input int SL=200; // Stop Loss in Punti
input int TP=1000; // TP in Punti

Bene, abbiamo i nostri parametri input per gestire i valori del ciclo che ci creerà i nostri ordini pendenti.
Quando parte la griglia, vorrò inserire, sopra il prezzo, un ordine SELL LIMIT, poi un BUY STOP, poi un SELL LIMIT, poi un BUY STOP, eccetera. Tutti distanti 10 pips l’uno dall’altro.

Se la logica e la matematica non ci ingannano, la distanza dei BUY STOP tra di loro sarà quindi doppia rispetto alla distanza input. Teniamolo in considerazione, e scriviamo il loop.

 for(int i=1; i<=MaxPendingOrders; i++)
     {

     }

Cominciamo a contare da 1, e arriviamo a contare fino al massimo degli ordini pendenti per tipo che vogliamo. Se è 5, il loop conterà da 1 a 5, elaborando il codice al suo interno per 5 volte. Parto da 1 e non da 0, perché utilizzerò l’indice (int i=1) per far fare alla macchina i calcoli di stop loss e take profit, oltre che per individuare il prezzo per inviare l’ordine. Cominciamo con il calcolare quindi i tre valori (prezzo, SL e TP)

 for(int i=1; i<=MaxPendingOrders; i++)
     {
      double buystopprice=price+i*2*DistanceOfGridOrders*_Point;
      double BSSL=buystopprice-SL*_Point;
      double BSTP=buystopprice+TP*_Point;
     }

analizziamo: buystopprice viene calcolato automaticamente partendo dal price (iClose, calcolato prima e identico per tutta la griglia) , aggiungendo il doppio della distanza tra gli ordini moltiplicato per il numero di iterazione che stiamo facendo. quindi al primo ciclo l’operazione è:

LIVELLO BUY STOP = PREZZO * 1 * 2 * DISTANZA GRIGLIA

Mentre nella 5a iterazione il valore sarà:

LIVELLO BUY STOP = PREZZO * 5 * 2 * DISTANZA GRIGLIA

L’ultima moltiplicazione, quella che dice *_Point, trasforma il valore intero inserito in input in un valore in punti, aggiungendo i decimali che servono per avere davvero 20 pips di distanza e non 20’000.

Ora che ci siamo calcolati il prezzo in modo dinamico, possiamo facilmente aggiungere i due livelli di TP e SL, sottraendo lo SL e aggiungendo il TP, sempre moltiplicando il valore in input per *_Point.

Lanciamo quindi il nostro ordine per ogni interazione.

 for(int i=1; i<=MaxPendingOrders; i++)
     {
      double buystopprice=price+i*2*DistanceOfGridOrders*_Point;
      double BSSL=buystopprice-SL*_Point;
      double BSTP=buystopprice+TP*_Point;
  
    int BSticket=OrderSend(_Symbol,OP_BUYSTOP,lots,buystopprice,3,BSSL,BSTP,
      _Symbol+" BUY STOP NUMBER: "+IntegerToString(i),MAGIC,0,clrGreen);
      
      if(BSticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",BSticket);
        }
     }

Salviamo il ticket nella variabile di tipo INT BSticket e dopo aver inviato l’ordine con OrderSend(), controlliamo che sia andato a buon fine. Altrimenti printa l’errore in console, in modo da capire dove abbiamo eventualmente sbagliato.

Da notare che il commento è costruito dinamicamente, ci dirà infatti:

  • Il Cross su cui avremo inviato la griglia
  • Il tipo di ordine
  • Il numero di ordine

Questo ci permetterà di orientarci meglio nella sezione “Operazioni” della Meta Trader

Bene. Procediamo con gli ordini SELL STOP, che rispondono alla stessa logica, ma dovremo semplicemente invertire alcuni segni + e – .

Ecco la funzione aggiornata:

 for(int i=1; i<=MaxPendingOrders; i++)
     {
      //CALCOLO I LIVELLI DEGLI ORDINI BUY STOP
      double buystopprice=price+i*2*DistanceOfGridOrders*_Point;
      double BSSL=buystopprice-SL*_Point;
      double BSTP=buystopprice+TP*_Point;
    
      //INVIO L'ORDINE A MERCATO
    
    int BSticket=OrderSend(_Symbol,OP_BUYSTOP,lots,buystopprice,3,BSSL,BSTP,
      _Symbol+" BUY STOP NUMBER: "+IntegerToString(i),MAGIC,0,clrGreen);
      
      //CONTROLLO CHE SIA ANDATO A BUON FINE
      if(BSticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",BSticket);
        }

      //CALCOLO I LIVELLI DEGLI ORDINI SELL STOP
      double sellstopprice=price-i*2*DistanceOfGridOrders*_Point;
      double SSSL=sellstopprice+SL*_Point;
      double SSTP=sellstopprice-TP*_Point;
   
      // INVIO ORDINE A MERCATO 
int SSticket=OrderSend(_Symbol,OP_SELLSTOP,lots,sellstopprice,3,SSSL,SSTP,
     _Symbol+" SELL STOP NUMBER: "+IntegerToString(i),MAGIC,0,clrGreen);
     
      //CONTROLLO CHE SIA ANDATO A BUON FINE      
 
      if(SSticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",SSticket);
        }

     }

Bene. Per evitare il sovraccarico della piattaforma (e permettere al broker di processare correttamente tutti gli ordini) inseriamo una funzione Sleep(2000); dopo ogni controllo di buon fine. In questo modo il processo è sempre molto semplice:

  1. Calcolo i nuovi livelli
  2. Inserisco l’ordine a mercato
  3. Controllo se è andato a buon fine
  4. Aspetto 2000 millisecondi (2 secondi)

Ora passiamo agli ordini LIMIT. Anche loro avranno una distanza tra di loro pari a 2 volte la distanza di input, ma questa volta partiranno da +\ – la distanza di input.

Bella rogna… come faccio a scrivere che devono partire a +tot, e poi piazzare gli ordini a +2*tot? Aggiungo la mia distanza di input? No, perché avendo il ciclo che parte da int i=1, i miei calcoli la porteranno naturalmente in un livello sbagliato.

IDEA!

Faccio i calcoli come al solito, ma SOTTRAGGO la mia distanza input. Insomma, per ottenere i valori “1,3,5,7,9” altro non faccio che trasformarli in “2-1,4-1,6-1,8-1 e 10-1

Bomba. Scriviamolo!

 for(int i=1; i<=MaxPendingOrders; i++)
     {
      //CALCOLO I LIVELLI DEGLI ORDINI BUY STOP
      double buystopprice=price+i*2*DistanceOfGridOrders*_Point;
      double BSSL=buystopprice-SL*_Point;
      double BSTP=buystopprice+TP*_Point;
    
      //INVIO L'ORDINE A MERCATO
    
    int BSticket=OrderSend(_Symbol,OP_BUYSTOP,lots,buystopprice,3,BSSL,BSTP,
      _Symbol+" BUY STOP NUMBER: "+IntegerToString(i),MAGIC,0,clrGreen);
      
      //CONTROLLO CHE SIA ANDATO A BUON FINE
      if(BSticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",BSticket);
        }
Sleep(2000);

      //CALCOLO I LIVELLI DEGLI ORDINI SELL STOP
      double sellstopprice=price-i*2*DistanceOfGridOrders*_Point;
      double SSSL=sellstopprice+SL*_Point;
      double SSTP=sellstopprice-TP*_Point;
   
      // INVIO ORDINE A MERCATO 
int SSticket=OrderSend(_Symbol,OP_SELLSTOP,lots,sellstopprice,3,SSSL,SSTP,
     _Symbol+" SELL STOP NUMBER: "+IntegerToString(i),MAGIC,0,clrGreen);
     
      //CONTROLLO CHE SIA ANDATO A BUON FINE      
 
      if(SSticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",SSticket);
        }
Sleep(2000);

//CALCOLO I LIVELLI DEGLI ORDINI BUY LIMIT
      double buylimitprice=price-i*2*DistanceOfGridOrders*_Point+DistanceOfGridOrders*_Point;
      double BLSL=buylimitprice-SL*_Point;
      double BLTP=buylimitprice+TP*_Point;
// INVIO A MERCATO 
  int BLticket=OrderSend(_Symbol,OP_BUYLIMIT,lots,buylimitprice,3,BLSL,BLTP,_Symbol+" BUY LIMIT NUMBER: "+IntegerToString(i),MAGIC,0,clrGreen);
// CONTROLLO 
 if(BLticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",BLticket);
        }
      Sleep(2000);


      double selllimitprice=price+i*2*DistanceOfGridOrders*_Point-DistanceOfGridOrders*_Point;
      double SLSL=selllimitprice+SL*_Point;
      double SLTP=selllimitprice-TP*_Point;

      int SLticket=OrderSend(_Symbol,OP_SELLLIMIT,lots,selllimitprice,3,SLSL,SLTP,_Symbol+" SELL LIMIT NUMBER: "+IntegerToString(i),MAGIC,expiration,clrGreen);
      if(SLticket<=0)
         Print("Error = ",GetLastError());
      else
        {
         Print("Order sent with ticket: ",SLticket);
        }
      Sleep(1000);
     }

E con questo la nostra maxi funzione è ultimata.

ATTENZIONE: Questa funzione funziona (ahahahah) solo sulle coppie di valute. Sugli indici è necessario inserire gli arrotondamenti al mezzo punto o al punto intero in base alla caratteristica di ciascun broker. NON verrà coperta questa parte in questo articolo. Ocio!

Bene… e come la usiamo? Non abbiamo assolutamente inserito un evento che faccia partire la funzione stessa. A me piacciono i bottoni. Usiamo la funzione che troviamo nel book mql4, brutalmente copiata ed incollata. La funzione è molto ben scritta e anche se non utilizziamo tutte le variabili richieste ha i suoi bei valori di default. Love it!

Creiamo la funzione che crea i pulsanti (copia e incolla)

bool ButtonCreate(const long              chart_ID=0,               // chart's ID
                  const string            name="Button",            // button name
                  const int               sub_window=0,             // subwindow index
                  const int               x=0,                      // X coordinate
                  const int               y=0,                      // Y coordinate
                  const int               width=50,                 // button width
                  const int               height=18,                // button height
                  const ENUM_BASE_CORNER  corner=CORNER_LEFT_UPPER, // chart corner for anchoring
                  const string            text="Button",            // text
                  const string            font="Arial",             // font
                  const int               font_size=10,             // font size
                  const color             clr=clrBlack,             // text color
                  const color             back_clr=C'236,233,216',  // background color
                  const color             border_clr=clrNONE,       // border color
                  const bool              state=false,              // pressed/released
                  const bool              back=false,               // in the background
                  const bool              selection=false,          // highlight to move
                  const bool              hidden=true,              // hidden in the object list
                  const long              z_order=0)                // priority for mouse click
  {
//--- reset the error value
   ResetLastError();
//--- create the button
   if(!ObjectCreate(chart_ID,name,OBJ_BUTTON,sub_window,0,0))
     {
      Print(__FUNCTION__,
            ": failed to create the button! Error code = ",GetLastError());
      return(false);
     }
//--- set button coordinates
   ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);
//--- set button size
   ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);
   ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);
//--- set the chart's corner, relative to which point coordinates are defined
   ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,corner);
//--- set the text
   ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
//--- set text font
   ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
//--- set font size
   ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
//--- set text color
   ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
//--- set background color
   ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);
//--- set border color
   ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_COLOR,border_clr);
//--- display in the foreground (false) or background (true)
   ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
//--- set button state
   ObjectSetInteger(chart_ID,name,OBJPROP_STATE,state);
//--- enable (true) or disable (false) the mode of moving the button by mouse
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
   ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
//--- hide (true) or display (false) graphical object name in the object list
   ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);
//--- set the priority for receiving the event of a mouse click in the chart
   ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
//--- successful execution
   return(true);
  }

Inseriamola dopo l’ultima graffa della nostra funzione di apertura della griglia.

E, sopratutto, CREIAMO I BOTTONI! Quando? Beh, appena avviamo l’EA, quindi all’interno della funzione OnInit():

int OnInit()
  {
//---
   ButtonCreate(0,"Apply Grid",0,30,30,80,20,CORNER_LEFT_LOWER,"Start Grid");
   ButtonCreate(0,"Close Profit",0,115,30,80,20,CORNER_LEFT_LOWER,"Close Profit");
   ButtonCreate(0,"Close All",0,200,30,80,20,CORNER_LEFT_LOWER,"Close All");
   ButtonCreate(0,"Delete Pendings",0,285,30,80,20,CORNER_LEFT_LOWER,"Del Pendings");
   ButtonCreate(0,"Clear Arrows",0,370,30,80,20,CORNER_LEFT_LOWER,"Clear Arrows");

//---
   return(INIT_SUCCEEDED);
  }

Li creiamo tutti e quattro così poi andremo a lavorare direttamente sulle funzioni. In buona sostanza ho scritto di creare 5 bottoni, ciascuno dei quali ha il suo bel nome e a cui attaccheremo la sua funzione successivamente. Ora la Meta Trader ha solo creato degli oggetti. Non fanno nulla se non schiacciarsi e rilasciarsi, se ci clicchiamo sopra.

Agevolo Screenshot:

SALVA IL FILE! Se non l’hai ancora fatto sappi che il Demonio sogghigna ai programmatori che non salvano.

Attacchiamo la funzione di apertura griglia al nostro pulsante

Bene. Ti ricordi che all’inizio del tutorial ti ho fatto spuntare la funzione “OnChartEvent()”? Ecco, questa è una funzione che “ascolta” ciò che succede sui grafici dal punto di vista grafico. Perdona il gioco di parole.

Noi dovremo fare in modo che quando schisciamo il butòn si attivi la funzione che abbiamo creato. Lo facciamo dentro la funzione OnChartEvent():

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(sparam=="Apply Grid")
        {
         StartGrid();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
}

La funzione raccoglie un po’ di informazioni sull’oggetto che abbiamo cliccato. A noi interessa il sparam (string parameter), che è un parametro stringa e contiente il NOME del nostro oggetto. Il concetto è:

  1. SE hai cliccato un oggetto
  2. SE questo oggetto si chiama “Apply Grid”
  3. Richiama la funzione StartGrid()
  4. Poi resetta lo stato dell’oggetto a false (non cliccato)

Semplice no?

Con un F7 e un F5 da tastiera compiliamo il codice e lo testiamo su una chart dal vivo. Non è magnifico?

Altre funzioni: Close At Profit

Vogliamo che le operazioni in profitto si chiudano automaticamente al raggiungimento di un profitto predeterminato.
Questa funzione permette di chiudere le operazioni in profit, inserendo manualmente un target monetario di profitto.

Cominciamo con l’inserire una variabile input di tipo double, che ci permetta di settare il profitto monetario, posizionata insieme alle altre variabili input, prima dell’OnInit():

input double Profit=10.0; // profitto per chiusura automatica delle posizioni

E poi scriviamo il ciclo che passa in rassegna tutti gli ordini aperti a mercato, controlla che siano quelli del nostro Expert Advisor, che siano operativi sul nostro cross, e che siano in profitto al netto di commissioni e swap. La chiameremo CloseAtProfit():

void CloseAtProfit(double _Profit)
  {
   int tot=OrdersTotal()-1;
   for(int i=tot; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
        {
         Print("Failed to select Order, Error: "+IntegerToString(GetLastError()));
        }
      else
        {
         if(OrderSymbol()==_Symbol &&OrderMagicNumber()==MAGIC)
           {
            if(OrderProfit()+OrderCommission()+OrderSwap()>=_Profit)
              {
               if(OrderType()==OP_BUY)
                 {
                  bool close=OrderClose(OrderTicket(),OrderLots(),Bid,3,clrGreen);
                 }

               if(OrderType()==OP_SELL)
                 {
                  bool close=OrderClose(OrderTicket(),OrderLots(),Ask,3,clrGreen);
                 }
              }
           }
        }
     }
  }

Tra le parentesi della funzione ho inserito un parametro da aggiungere quando chiameremo la funzione: double _Profit. Questo ci permette di riutilizzare questa funzione ovunque vorremo, inserendo il profitto (in valuta conto) che vogliamo raggiungere.

La spiegazione tradotta in italiano del ciclo suona così:

  1. Prendi tutti gli ordini aperti a mercato
  2. Selezionane uno per volta
  3. SE l’ordine selezionato è sul nostro cross
  4. SE l’ordine selezionato è stato aperto dal nostro EA (OrderMagicNumber()==MAGIC)
  5. SE la somma tra il profitto, le commissioni e lo swap è MAGGIORE o UGUALE del nostro target monetario
  6. Allora chiudi la posizione (se è BUY, usando il BID, se è SELL usando l’ASK)

Anche qui… semplice e veloce ciclo for per spulciare tra gli ordini aperti.

Dove la chiamiamo questa funzione? Beh, noi dobbiamo chiudere non appena almeno una operazione è in profitto desiderato, quindi la inseriremo nell’OnTick() così ad ogni variazione di prezzo, il nostro EA sceglierà le posizioni da chiudere. L’OnTick() ora si presenta così:

void OnTick()
  {
//---
   CloseAtProfit(Profit);
  }

Cosa manca? Beh, abbiamo altri 4 pulsanti! Usiamoli per Dio!

Altre funzioni: chiudere tutti i pendenti

Questa funzione ci permette di chiudere in un click tutti i pendenti.

Attenzione! Per i pendenti non si deve usare OrderClose bensì OrderDelete(). Il ciclo per la loro selezione è semplice:

void ClearPending()
  {
   int tot=OrdersTotal()-1;
   for(int i=tot; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
        {
         Print("Failed to select Order, Error: "+IntegerToString(GetLastError()));
        }
      else
        {
         if(OrderSymbol()==_Symbol && OrderMagicNumber()==MAGIC)
           {
            if(OrderType()>=2)
              {
               bool del=OrderDelete(OrderTicket(),clrNONE);
              }
           }
        }
     }
  }

Anche qui, la traduzione del ciclo è semplice:

  1. Prendi un ordine alla volta
  2. SE l’ordine selezionato è su questo cross
  3. SE l’ordine selezionato è aperto da questo EA
  4. SE l’ordine è di tipo >= 2
  5. Chiudi tutto, Miss Italia per te finisce qui

Occhio al punto 4: Per la META TRADER i diversi tipi di ordine corrispondono ad un numero intero compreso tra 0 e 5, come si vede in questa immagine:

Queste si chiamano costanti di piattaforma. In qualsiasi programma potrai usarle per riconoscere facilmente il tipo di ordine.

In questo caso, per indicare tutti i pendenti, basta che il tipo di ordine sia >= 2 😉 Nice!

Dove la cacciamo questa funzione? Beh, va abbinata ad un pulsante!

Bravo, ‘nduinato! Nella funzione OnChartEvent()! Dentro l’IF dell’OBJECT_CLICK

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(sparam=="Apply Grid")
        {
         StartGrid();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Delete Pendings")
        {
         ClearPending();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }

}

Bomba. Ora possiamo aprire 20 pendenti per tipo per lato, senza sforzo, e chiudere tutte le operazioni non ancora aperte ancora senza sforzo.

Amo la programmazione.

Funzione killer: Mass Close

E che ne dici se ci scriviamo una funzione che chiuda qualsiasi posizione?

Rifacciamo il nostro ciclo degli OrdersTotal() e indipendentemente da altri parametri chiudiamo l’operazione. La chiameremo CloseAny():

void CloseAny()
  {
   int tot=OrdersTotal()-1;
   for(int i=tot; i>=0; i--)
     {
      if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
        {
         Print("Failed to select Order, Error: "+IntegerToString(GetLastError()));
        }
      else
        {
         if(OrderSymbol()==_Symbol &&OrderMagicNumber()==MAGIC)
           {
            if(OrderType()==OP_BUY)
              {
               bool close=OrderClose(OrderTicket(),OrderLots(),Bid,3,clrGreen);
              }

            if(OrderType()==OP_SELL)
              {
               bool close=OrderClose(OrderTicket(),OrderLots(),Ask,3,clrGreen);
              }
            if(OrderType()>=2)
              {
               bool del=OrderDelete(OrderTicket(),clrNONE);
              }
           }
        }
     }
  }

Questa chiuderà ogni operazione su grafico. Sia aperta che pendente. Appunto. è la bomba atomica del grafico. Lo Sterilizza. Insomma.. ci siamo capiti.

Quando la usiamo? Ma con il nostro bottoncino naturalmente!

Aggiungiamo nell’OnChartEvent() la nostra funzione:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(sparam=="Apply Grid")
        {
         StartGrid();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Delete Pendings")
        {
         ClearPending();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Close All")
        {
         CloseAny();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
     }
  }

Altre Funzioni: Chiudere gli ordini in profitto

Ci potrebbe servire una funzione che chiuda tutti gli ordini in profitto. Ma ehy! Abbiamo già scritto una cosa simile! Ricordi? Era il nostro CloseAtProfit(). Basterà inserire “0” come valore passato alla funzione ed avremo la chiusura di tutte le posizioni che, al netto di swap e commissioni, hanno un profitto maggiore di 0. Nice! Del codice non si butta via niente!

Allora, ci basta aggiungere la funzione abbinata al pulsante corretto, nel ciclo OnChartEvent!

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(sparam=="Apply Grid")
        {
         StartGrid();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Delete Pendings")
        {
         ClearPending();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Close All")
        {
         CloseAny();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Close Profit")
        {
         CloseAtProfit(0);
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
     }
  }

Fico! Fine.

Ah, no… Hai presente cosa vuol dire avere su grafico 20, 40, 60 elementi grafici creati dagli ordini? Parlo delle freccine e dei trattini che indicano ingresso, Take Profit e Stop Loss. Ecco… se hai 60 ordini in griglia avrai 180 oggetti.

A me piace pulire il grafico senza chiudere gli ordini. Come si fa?

Altre funzioni: eliminare gli oggetti grafici

Meta Trader ci regala una funzione che fa al caso nostro: la ObjectsDeleteAll(), che con gli opportuni parametri farà tutto il lavoro sporco.

La inseriamo direttamente nell’OnCHartEvent() perché è talmente breve da non necessitare una funzione scritta a sé stante

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(sparam=="Apply Grid")
        {
         StartGrid();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Delete Pendings")
        {
         ClearPending();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Close All")
        {
         CloseAny();
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Close Profit")
        {
         CloseAtProfit(1);
         ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
        }
      if(sparam=="Clear Arrows")
        {
         ObjectsDeleteAll(0,0,OBJ_ARROW);
         ObjectsDeleteAll(0,0,OBJ_TREND);
         ObjectSetInteger(0,"Clear Arrows",OBJPROP_STATE,false);
        }
     }
  }

E con questa anche l’ultimo pulsante andrà a funzionare, eliminando le fastidiose freccine dalla nostra chart. Semplice e veloce.

Considerazioni finali e codice completo

Questo Expert Advisor è assolutamente fallimentare, nel senso che NON ti aiuterà ad essere profittevole sui mercati. Non escludo che tu possa farci qualche pip in demo, per divertimento, come è capitato a me.

L’importante è che, con un PROBLEMA PRATICO, abbiamo insieme scoperto e approfondito alcuni concetti, come ad esempio

  • Selezionare gli ordini e scartarli se non hanno alcune caratteristiche
  • Chiudere ed aprire ordini
  • Usare un for loop per le attività ricorrenti
  • Calcolare dinamicamente dei livelli di prezzo
  • Attivare dei pulsanti
  • Scoprire quali pulsanti hai cliccato
  • Chiudere in profitto le operazioni aperte
  • Chiudere tutte le operazioni

E direi che ci sono parecchie informazioni che ti torneranno utili per i tuoi Expert Advisor e i tuoi progetti Mql4.

Ti lascio il codice completo in fondo all’articolo 🙂 Fanne buon uso!

Cosa ne dici di provare ad inserire autonomamente un nuovo pulsante per chiudere tutte le posizioni in PERDITA? Provaci da solo e scrivimi nei commenti se ce l’hai fatta!

Segui la pagina Facebook Automazione Trading, lasciami un like, è molto apprezzato per l’enorme lavoro che implica scrivere una guida come questa.

Happy forex e Good Gains!

Se ti è piaciuto l’articolo, condividilo!

CODICE COMPLETO

//+------------------------------------------------------------------+
//|                                                GridSTOPLIMIT.mq4 |
//|                                                 D'ario Woollover |
//|                               https://www.automazionetrading.com |
//+------------------------------------------------------------------+
#property copyright "D'ario Woollover"
#property link      "https://www.automazionetrading.com"
#property version   "2.00"
#property strict
#define MAGIC 42
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
input double INP_lots=0.01; // lotti griglia
input int DistanceOfGridOrders=100; // distanza in punti della griglia
input int MaxPendingOrders=5; // Numero massimo ordini (per lato, per tipo)
input int SL=200; // Stop Loss in Punti
input int TP=1000; // TP in Punti
input double Profit=10.0; // profitto per chiusura automatica delle posizioni
datetime expiration;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
ButtonCreate(0,"Apply Grid",0,30,30,80,20,CORNER_LEFT_LOWER,"Start Grid");
ButtonCreate(0,"Close Profit",0,115,30,80,20,CORNER_LEFT_LOWER,"Close Profit");
ButtonCreate(0,"Close All",0,200,30,80,20,CORNER_LEFT_LOWER,"Close All");
ButtonCreate(0,"Delete Pendings",0,285,30,80,20,CORNER_LEFT_LOWER,"Del Pendings");
ButtonCreate(0,"Clear Arrows",0,370,30,80,20,CORNER_LEFT_LOWER,"Clear Arrows");
expiration=TimeCurrent()+PERIOD_W1*60;
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
ObjectsDeleteAll(0,0,OBJ_BUTTON);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//---
CloseAtProfit(Profit);
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
//---
if(id==CHARTEVENT_OBJECT_CLICK)
{
if(sparam=="Apply Grid")
{
StartGrid();
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
}
if(sparam=="Delete Pendings")
{
ClearPending();
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
}
if(sparam=="Close All")
{
CloseAny();
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
}
if(sparam=="Close Profit")
{
CloseAtProfit(1);
ObjectSetInteger(0,sparam,OBJPROP_STATE,false);
}
if(sparam=="Clear Arrows")
{
ObjectsDeleteAll(0,0,OBJ_ARROW);
ObjectsDeleteAll(0,0,OBJ_TREND);
ObjectSetInteger(0,"Clear Arrows",OBJPROP_STATE,false);
}
}
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void StartGrid()
{
double price=iClose(_Symbol,PERIOD_CURRENT,0);
double maxlot=MarketInfo(_Symbol,MODE_MAXLOT);
double minlot=MarketInfo(_Symbol,MODE_MINLOT);
double lots;
lots = (INP_lots>=maxlot) ? lots=maxlot : lots=INP_lots;
lots = (INP_lots<=minlot) ? lots=minlot: lots=INP_lots;
for(int i=1; i<=MaxPendingOrders; i++)
{
double buystopprice=price+i*2*DistanceOfGridOrders*_Point;
double BSSL=buystopprice-SL*_Point;
double BSTP=buystopprice+TP*_Point;
double sellstopprice=price-i*2*DistanceOfGridOrders*_Point;
double SSSL=sellstopprice+SL*_Point;
double SSTP=sellstopprice-TP*_Point;
int BSticket=OrderSend(_Symbol,OP_BUYSTOP,lots,buystopprice,3,BSSL,BSTP,_Symbol+" BUY STOP NUMBER: "+IntegerToString(i),MAGIC,expiration,clrGreen);
if(BSticket<=0)
Print("Error = ",GetLastError());
else
{
Print("Order sent with ticket: ",BSticket);
}
Sleep(1000);
int SSticket=OrderSend(_Symbol,OP_SELLSTOP,lots,sellstopprice,3,SSSL,SSTP,_Symbol+" SELL STOP NUMBER: "+IntegerToString(i),MAGIC,expiration,clrGreen);
if(SSticket<=0)
Print("Error = ",GetLastError());
else
{
Print("Order sent with ticket: ",SSticket);
}
Sleep(1000);
double buylimitprice=price-i*2*DistanceOfGridOrders*_Point+DistanceOfGridOrders*_Point;
double BLSL=buylimitprice-SL*_Point;
double BLTP=buylimitprice+TP*_Point;
int BLticket=OrderSend(_Symbol,OP_BUYLIMIT,lots,buylimitprice,3,BLSL,BLTP,_Symbol+" BUY LIMIT NUMBER: "+IntegerToString(i),MAGIC,expiration,clrGreen);
if(BLticket<=0)
Print("Error = ",GetLastError());
else
{
Print("Order sent with ticket: ",BLticket);
}
Sleep(1000);
double selllimitprice=price+i*2*DistanceOfGridOrders*_Point-DistanceOfGridOrders*_Point;
double SLSL=selllimitprice+SL*_Point;
double SLTP=selllimitprice-TP*_Point;
int SLticket=OrderSend(_Symbol,OP_SELLLIMIT,lots,selllimitprice,3,SLSL,SLTP,_Symbol+" SELL LIMIT NUMBER: "+IntegerToString(i),MAGIC,expiration,clrGreen);
if(SLticket<=0)
Print("Error = ",GetLastError());
else
{
Print("Order sent with ticket: ",SLticket);
}
Sleep(1000);
}
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseAny()
{
int tot=OrdersTotal()-1;
for(int i=tot; i>=0; i--)
{
if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
{
Print("Failed to select Order, Error: "+IntegerToString(GetLastError()));
}
else
{
if(OrderSymbol()==_Symbol &&OrderMagicNumber()==MAGIC)
{
if(OrderType()==OP_BUY)
{
bool close=OrderClose(OrderTicket(),OrderLots(),Bid,3,clrGreen);
}
if(OrderType()==OP_SELL)
{
bool close=OrderClose(OrderTicket(),OrderLots(),Ask,3,clrGreen);
}
if(OrderType()>=2)
{
bool del=OrderDelete(OrderTicket(),clrNONE);
}
}
}
}
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseAtProfit(double _Profit)
{
int tot=OrdersTotal()-1;
for(int i=tot; i>=0; i--)
{
if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
{
Print("Failed to select Order, Error: "+IntegerToString(GetLastError()));
}
else
{
if(OrderSymbol()==_Symbol &&OrderMagicNumber()==MAGIC)
{
if(OrderProfit()+OrderCommission()+OrderSwap()>=_Profit)
{
if(OrderType()==OP_BUY)
{
bool close=OrderClose(OrderTicket(),OrderLots(),Bid,3,clrGreen);
}
if(OrderType()==OP_SELL)
{
bool close=OrderClose(OrderTicket(),OrderLots(),Ask,3,clrGreen);
}
}
}
}
}
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ClearPending()
{
int tot=OrdersTotal()-1;
for(int i=tot; i>=0; i--)
{
if(!OrderSelect(i,SELECT_BY_POS,MODE_TRADES))
{
Print("Failed to select Order, Error: "+IntegerToString(GetLastError()));
}
else
{
if(OrderSymbol()==_Symbol && OrderMagicNumber()==MAGIC)
{
if(OrderType()>=2)
{
bool del=OrderDelete(OrderTicket(),clrNONE);
}
}
}
}
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool ButtonCreate(const long              chart_ID=0,               // chart's ID
const string            name="Button",            // button name
const int               sub_window=0,             // subwindow index
const int               x=0,                      // X coordinate
const int               y=0,                      // Y coordinate
const int               width=50,                 // button width
const int               height=18,                // button height
const ENUM_BASE_CORNER  corner=CORNER_LEFT_UPPER, // chart corner for anchoring
const string            text="Button",            // text
const string            font="Arial",             // font
const int               font_size=10,             // font size
const color             clr=clrBlack,             // text color
const color             back_clr=C'236,233,216',  // background color
const color             border_clr=clrNONE,       // border color
const bool              state=false,              // pressed/released
const bool              back=false,               // in the background
const bool              selection=false,          // highlight to move
const bool              hidden=true,              // hidden in the object list
const long              z_order=0)                // priority for mouse click
{
//--- reset the error value
ResetLastError();
//--- create the button
if(!ObjectCreate(chart_ID,name,OBJ_BUTTON,sub_window,0,0))
{
Print(__FUNCTION__,
": failed to create the button! Error code = ",GetLastError());
return(false);
}
//--- set button coordinates
ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x);
ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y);
//--- set button size
ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width);
ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height);
//--- set the chart's corner, relative to which point coordinates are defined
ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,corner);
//--- set the text
ObjectSetString(chart_ID,name,OBJPROP_TEXT,text);
//--- set text font
ObjectSetString(chart_ID,name,OBJPROP_FONT,font);
//--- set font size
ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size);
//--- set text color
ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr);
//--- set background color
ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr);
//--- set border color
ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_COLOR,border_clr);
//--- display in the foreground (false) or background (true)
ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back);
//--- set button state
ObjectSetInteger(chart_ID,name,OBJPROP_STATE,state);
//--- enable (true) or disable (false) the mode of moving the button by mouse
ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection);
ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection);
//--- hide (true) or display (false) graphical object name in the object list
ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden);
//--- set the priority for receiving the event of a mouse click in the chart
ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order);
//--- successful execution
return(true);
}
//+------------------------------------------------------------------+

Condividi il post se ti è piaciuto

Articolo creato 11

Un commento su “Un Expert Advisor per gestire semplici griglie

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Articoli correlati

Inizia a scrivere il termine ricerca qua sopra e premi invio per iniziare la ricerca. Premi ESC per annullare.

Torna in alto
Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert
shares