Come creare il Tuo Trailing Stop

Dmitry Fedoseev | 9 dicembre, 2021

Introduzione

Prima di iniziare con l'oggetto dell'articolo, credo che sia una buona idea mettere i puntini sulle i. Ancora una volta, definiamo i termini "posizione" e "ordine":

 Figura 1. Posizioni e ordini.

Figura 1. Posizioni e Ordini.

Questo articolo si concentra sul livello di trailing Stop Loss per le posizioni. Per gli ordini in sospeso, questa operazione non ha senso perché puoi passare direttamente al prezzo dell'ordine. Quando si trasforma in una posizione (o in una sua parte), allora questo materiale sarà utile .

La posizione di trading può essere chiusa non solo premendo il pulsante "Chiudi" nella finestra di dialogo della posizione (Figura 2).

 Figura 2. Chiusura di una posizione utilizzando il pulsante "Chiudi" nella finestra di dialogo della posizione.

Figura 2. Chiusura di una posizione utilizzando il pulsante "Chiudi" nella finestra di dialogo della posizione. 1 - apri il menu contestuale della posizione, 2 - seleziona "Chiudi posizione" 
3 - fare clic sul pulsante "Chiudi".

Inoltre, la posizione può essere chiusa automaticamente quando il prezzo raggiunge un livello di profitto (Take Profit) o di perdita (Stop Loss) predeterminato. A differenza della chiusura della posizione utilizzando il pulsante "Chiudi", la chiusura tramite Stop Loss e Take Profit non viene effettuata dal terminale (dal trader o dall'expert), bensì dal broker. Pertanto, la posizione di chiusura è completamente garantita, indipendentemente dalla connessione e dall'alimentazione elettrica. Ciò rende l'uso dello Stop Loss un elemento pressoché obbligatorio nel lavoro del trader.

L'unica azione che il trader dovrebbe effettuare è dare un ordine al broker per impostare il livello di stop protettivo. In altre parole, devi impostare Stop Loss sulla posizione (o posizione aperta con questo livello impostato). L'impostazione dello Stop Loss viene eseguita utilizzando il comando del menu contestuale "Modifica" nel terminale. Nell'elenco delle posizioni, seleziona una posizione, fai click con il pulsante destro del mouse e scegli "Modifica o Elimina". Quindi, nella finestra di dialogo della posizione è necessario inserire il livello necessario di Stop Loss e fare click su "Modifica" (Figura 3).

Figura 3. Impostazione del Livello di Stop Loss della Posizione.

Figura 3. Impostazione del Livello di Stop Loss della Posizione. 1 - apri il menu contestuale della posizione, 2 - fai click su "Modifica o Elimina", 3 - imposta il valore, 4 - fai click su "Modifica". 

Il livello di Stop Loss della posizione viene mostrato sul grafico dei prezzi insieme al livello della sua apertura (Figura 4).

Figura 4. Posizione con Stop Loss. Il livello è contrassegnato con una linea tratteggiata rossa etichettata con sl nel suo bordo sinistro.

Figura 4. Posizione con Stop Loss. Il livello è contrassegnato da una linea tratteggiata rossa etichettata con sl nel suo bordo sinistro.

Non solo puoi installare Stop Loss per la posizione, ma anche modificarne periodicamente il valore. Ad esempio, puoi aumentarlo quando il prezzo cambia nella direzione redditizia, riducendo così la possibile perdita. Tale trazione del livello di protezione è nota come trailing stop.

Ci sono molte varianti di trailing stop: puoi semplicemente tirare lo Stop Loss dopo il prezzo a una determinata distanza. Puoi iniziare a muovere Stop Loss non immediatamente, ma quando la posizione raggiunge una certa redditività allora viene immediatamente spostata al livello di pareggio. Questa variante è standard ed è integrata nel client terminal MetaTrader 5. Per utilizzare la posizione di trailing stop standard, fare click con il pulsante destro del mouse e scegliere "Trailing Stop" (Figura 5).

Figura 5. Attivazione del trailing stop standard nel terminale.

Figura 5. Attivazione del trailing stop standard nel terminale. 1 - aprire il menu contestuale della posizione, 2 - fare click su "Trailing Stop", 3 - selezionare il valore (o impostare il valore).
Il comando di impostazione del valore (Personalizzato)
si trova nella parte inferiore del menu contestuale e non viene mostrato nell'immagine.

Oltre al monitoraggio diretto dei prezzi, il trailing stop può funzionare sulla base di alcuni indicatori tecnici. Ad esempio, basato su medie mobili, permettendo di non reagire alle variazioni di prezzo a breve termine, basato sull’indicatore Ichimoku o uno più appropriato, nonché sull'indicatore Parabolic SAR (Stop And Reverse) che inizialmente non è progettato per questo scopo. Vedere la Figura 6.  

Figura 6. Indicatore SAR Parabolico.

Figura 6. Indicatore SAR Parabolico. 

Nella programmazione procedurale MQL4, un trailing stop viene solitamente creato come funzione separata o viene integrato in altre funzioni. Ad esempio, nell'expert MACD Sample, incluso in MetaTrader 4, la funzione di trailing stop è integrata con la funzione di chiusura del mercato degli ordini:

for(cnt=0;cnt<total;cnt++)
  {
   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
        {
         // should it be closed?
         if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
            MacdCurrent>(MACDCloseLevel*Point))
           {
            OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
            return(0); // exit
           }
         // check for 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);
                 }
              }
           }
        }
      else // go to short position
        {
         // should it be closed?
         if(MacdCurrent<0 && MacdCurrent>SignalCurrent && 
            MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
           {
            OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
            return(0); // exit
           }
         // check for trailing stop
         if(TrailingStop>0) 
           {
             
            if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
              {
               if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
                 {
                  OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
                  return(0);
                 }
              }
           }
        }
     }
  }

MQL5 come Linguaggio Orientato agli Oggetti offre molte più possibilità nella progettazione di expert. Ti consente di creare classi versatili e multifunzionali che in seguito possono essere integrate rapidamente e facilmente in quasi tutti gli expert. In questo articolo, svilupperemo una classe di questo tipo.


1. Creazione di una Classe Base di Trailing Stop

Come accennato in precedenza, esiste un numero enorme di trailing stop, ma tutti hanno aspetti funzionali comuni:

Il tipo di trailing stop determinerà solo il valore del livello di Stop Loss calcolato. Pertanto, la funzionalità di base del trailing stop sarà inclusa nella classe base. Per la funzionalità, che dipende dal tipo di trailing stop, verranno create delle sottoclassi. L'applicazione ai metodi di queste sottoclassi avverrà tramite metodi virtuali della classe base.

Poiché stiamo progettando di utilizzare indicatori tecnici, per garantire il loro funzionamento stabile è necessario fornire loro un apparecchio periodico. A questo scopo utilizzeremo il timer. Abbiamo anche in programma di attivare/disattivare il trailing stop (quando si utilizza la classe come parte del sistema di trading meccanico) e di attivarla/disattivarla utilizzando l'oggetto grafico - pulsante (quando si utilizza la classe come parte di expert ausiliari). In base a questi requisiti funzionali, la classe base risulterà possedere il seguente insieme di metodi:

class CTrailingStop
  {
protected:
public:
   void CTrailingStop(){};
   void ~CTrailingStop(){};
   void Init(){};                   // Initialization of class
   bool StartTimer(){};             // Start timer
   void StopTimer(){};              // Stop timer
   void On(){};                     // Turn on trailing stop
   void Off(){};                    // Turn off trailing stop
   bool DoStoploss(){};             // Main method of controlling level of Stop Loss position
   void EventHandle(){};            // Method of processing chart events (pressing button to turn on trailing stop)
   void Deinit(){};                 // Deinitialization
   virtual bool Refresh(){};        // Refresh indicator
   virtual void Setparameters(){};  // Setting parameters and loading indicator
   virtual int Trend(){};           // Trend shown by indicator
   virtual double BuyStoploss(){};  // Stop Loss value for the Buy position
   virtual double SellStoploss(){}; // Stop Loss value for the Sell position
  };

Quando si chiama il metodo Init(), accetterà parametri generali che non dipendono dal tipo di trailing stop utilizzato. Il metodo imposterà la modalità trailing stop e preparerà le variabili con alcuni parametri di mercato.

1.1. Init() metodo

Il metodo Init() è il primo metodo, chiamato dopo aver creato un'istanza di classe. Accetta parametri generali, indipendenti dal tipo di trailing stop: simbolo, timeframe, modalità di trailing stop (per tick o per barre), per associare o non associare un indicatore al grafico, creare o non creare un pulsante. Quindi accetta le proprietà del pulsante: Coordinata X del pulsante, coordinata Y del pulsante, colore del pulsante, colore della didascalia del pulsante.

I parametri, necessari per ulteriori lavori, sono memorizzati nelle variabili della classe. Inoltre, quando il metodo Init() funziona, determina i principali parametri di mercato invariabili, necessari per il trailing stop: il numero di cifre dopo la virgola e il valore del punto. Infine, a seconda del tipo di trailing stop, si formano il nome del pulsante e la sua didascalia. Se è impostato per utilizzare un pulsante, viene creato. 

Nella sezione "protetta", dichiariamo tutte le variabili necessarie:

protected:
string m_symbol;             // symbol
ENUM_TIMEFRAMES m_timeframe; // timeframe
bool m_eachtick;             // work on each tick
bool m_indicator;            // show indicator on chart
bool m_button;               // show "turn on/turn off" button
int m_button_x;              // x coordinate of button
int m_button_y;              // y coordinate of button
color m_bgcolor;             // button color
color m_txtcolor;            // button caption color
int m_shift;                 // bar shift
bool m_onoff;                // turned on/turned off
int m_handle;                // indicator handle
datetime m_lasttime;         // time of trailing stop last execution
MqlTradeRequest m_request;   // trade request structure
MqlTradeResult m_result;     // structure of trade request result
int m_digits;                // number of digits after comma for price
double m_point;              // value of point
string m_objname;            // button name
string m_typename;           // name of trailing stop type
string m_caption;            // button caption

Ora, scriviamo il metodo Init() stesso:

//--- Trailing stop initialization method
void Init(string             symbol,
          ENUM_TIMEFRAMES timeframe,
          bool   eachtick  =   true,
          bool   indicator =  false,
          bool   button    =  false,
          int    button_x  =      5,
          int    button_y  =     15,
          color  bgcolor   = Silver,
          color  txtcolor  =   Blue)
  {
//--- set parameters
   m_symbol    = symbol;    // symbol
   m_timeframe = timeframe; // timeframe
   m_eachtick  = eachtick;  // true - work on each tick, false - false - work once per bar 
//--- set bar, from which indicator value is used
   if(eachtick)
     {
      m_shift=0; // created bar in per tick mode
     }
   else
     {
      m_shift=1; // created bar in per bar mode
     }
   m_indicator = indicator; // true - attach indicator to chart
   m_button    = button;    // true - create button to turn on/turn off trailing stop
   m_button_x  = button_x;  // x coordinate of button
   m_button_y  = button_y;  // y coordinate of button
   m_bgcolor   = bgcolor;   // button color
   m_txtcolor  = txtcolor;  // button caption color 
//--- get unchanged market history 
   m_digits=(int)SymbolInfoInteger(m_symbol,SYMBOL_DIGITS); // number of digits after comma for price
   m_point=SymbolInfoDouble(m_symbol,SYMBOL_POINT);         // value of point 
//--- creating button name and button caption
   m_objname="CTrailingStop_"+m_typename+"_"+symbol;        // button name
   m_caption=symbol+" "+m_typename+" Trailing";             // button caption 
//--- filling the trade request structure
   m_request.symbol=m_symbol;                               // preparing trade request structure, setting symbol
   m_request.action=TRADE_ACTION_SLTP;                      // preparing trade request structure, setting type of trade action
//--- creating button
   if(m_button)
     {
      ObjectCreate(0,m_objname,OBJ_BUTTON,0,0,0);                 // creating
      ObjectSetInteger(0,m_objname,OBJPROP_XDISTANCE,m_button_x); // setting x coordinate
      ObjectSetInteger(0,m_objname,OBJPROP_YDISTANCE,m_button_y); // setting y coordinate
      ObjectSetInteger(0,m_objname,OBJPROP_BGCOLOR,m_bgcolor);    // setting background color
      ObjectSetInteger(0,m_objname,OBJPROP_COLOR,m_txtcolor);     // setting caption color
      ObjectSetInteger(0,m_objname,OBJPROP_XSIZE,120);            // setting width
      ObjectSetInteger(0,m_objname,OBJPROP_YSIZE,15);             // setting height
      ObjectSetInteger(0,m_objname,OBJPROP_FONTSIZE,7);           // setting font size
      ObjectSetString(0,m_objname,OBJPROP_TEXT,m_caption);        // setting button caption 
      ObjectSetInteger(0,m_objname,OBJPROP_STATE,false);          // setting button state, turned off by default
      ObjectSetInteger(0,m_objname,OBJPROP_SELECTABLE,false);     // user can't select and move button, only click it
      ChartRedraw();                                              // chart redraw 
     }
//--- setting state of trailing stop
   m_onoff=false;                                                 // state of trailing stop - turned on/turned off, turned off by default 
  };

Puoi vedere che durante la creazione del nome e della didascalia del pulsante, viene utilizzata la variabile m_typenameche non è stata inizializzata con alcun valore. L'assegnazione di un valore verrà eseguita nei constructor della sottoclasse. Quindi, quando si utilizzano metodi diversi di trailing stop, esso avrà un valore diverso, corrispondente al tipo di trailing stop utilizzato. 

1.2. Metodo StartTimer()

Il metodo StartTimer() avvia il timer comune dell’expert.  

//--- Start timer
bool StartTimer()
  {
   return(EventSetTimer(1));
  };

Quando si utilizza il timer, è necessario aggiungere la chiamata del metodo Refresh() nella funzione OnTimer() per il richiamo periodico all'indicatore.

1.3. Metodo StopTimer()

Il metodo StartTimer() interrompe il timer di un expert.  

//--- Stop timer
void StopTimer()
  {
   EventKillTimer();
  };

Quando l'expert termina il suo lavoro, questo metodo interrompe il timer, se lo hai utilizzato. Questo metodo verrà chiamato durante l'esecuzione del metodo Deinit() di una classe.  

1.4. Il metodo On()

Il metodo On() attiva il trailing stop. L'accensione avviene assegnando il valore true alla variabile m_onoff. Se il pulsante è impostato per essere utilizzato nell'inizializzazione della classe, allora viene premuto. 

//--- Turn on trailing stop
void On()
  {
   m_onoff=true; 
   if(m_button)
     { // if button is used, it is "pressed"
      if(!ObjectGetInteger(0,m_objname,OBJPROP_STATE))
        {
         ObjectSetInteger(0,m_objname,OBJPROP_STATE,true);
        }
     }
  }

1.5. Metodo Off()

Il metodo Off() disattiva il trailing stop. La disattivazione avviene assegnando il valore false alla variabile m_onoff . Se il pulsante è impostato per essere utilizzato nell'inizializzazione della classe, allora viene premuto. 

//--- Turn off trailing stop
void Off()
  {
   m_onoff=false;
   if(m_button)
     { // if button is used, it is "depressed"
      if(ObjectGetInteger(0,m_objname,OBJPROP_STATE))
        {
         ObjectSetInteger(0,m_objname,OBJPROP_STATE,false);
        }
     }
  }

1.6. Metodo EventHandle()

Il metodo EventHandle() verrà chiamato dalla funzione OnChartEvent() e, di conseguenza, accetterà tutti i parametri passati alla funzione OnChartEvent().

//--- Method of tracking button state - turned on/turned off
void EventHandle(const int id,const long  &lparam,const double &dparam,const string &sparam)
  {
   if(id==CHARTEVENT_OBJECT_CLICK && sparam==m_objname)
     { // there is an event with button
      if(ObjectGetInteger(0,m_objname,OBJPROP_STATE))
        { // check button state
         On(); // turn on
        }
      else
        {
         Off(); // turn off
        }
     }
  }

Se si verifica l'evento CHARTEVENT_OBJECT_CLICK e questo evento si verifica con un pulsante che ha il nome m_objname (il nome dell'oggetto, con cui si è verificato l'evento, viene passato nella variabile sparam), allora a seconda dello stato del pulsante viene eseguito il metodo On() o Off() .

1.7. Metodo Deinit()

Il metodo Deinit() deve essere chiamato quando l’expert ha terminato il suo lavoro. Questo metodo interrompe il timer, rilascia l’handler dell'indicatore e cancella il pulsante, se è stato utilizzato. 

//--- Method of deinitialization
void Deinit()
  {
   StopTimer();                  // stop timer
   IndicatorRelease(m_handle);   // release indicator handle
   if(m_button)
     {
      ObjectDelete(0,m_objname); // delete button
      ChartRedraw();             // chart redraw
     }
  }

Dei metodi virtuali Refresh(), SetParameters(), Trend(), BuyStoploss() e SellStoploss() parleremo in seguito, quando creeremo le sottoclassi di trailing stop. Consideriamo ora il metodo principale della classe base: il metodo DoStoploss().

1.8. Metodo DoStoploss()

Il metodo DoStoploss() è il metodo di lavoro principale che deve essere chiamato dalla funzione OnTick() su ogni tick. Se il valore della variabile m_onoff è falso (il trailing stop è disattivato), allora il metodo termina immediatamente il suo lavoro.  

if(!m_onoff)
  {
   return(true);// if trailing stop is turned off
  }

Inoltre, se il trailing stop funziona in modalità per barra, viene controllato il tempo, confrontando il tempo della barra creata con il tempo dell'ultima esecuzione realizzata della funzione. Se il tempo corrisponde, allora il metodo termina il suo lavoro.

datetime tm[1];
// get the time of last bar in per bar mode 
if(!m_eachtick)
  { 
   // if unable to copy time, finish method, repeat on next tick 
   if(CopyTime(m_symbol,m_timeframe,0,1,tm)==-1)
     {
      return(false); 
     }
   // if the bar time is equal to time of method's last execution - finish method
   if(tm[0]==m_lasttime)
     { 
      return(true);
     }
  }

Se il trailing stop è attivato e il controllo del tempo è stato superato, allora viene eseguita la parte principale del metodo - i valori dell'indicatore vengono aggiornati (viene chiamato il metodo Refresh()).

if(!Refresh())
  { // get indicator values
   return(false);
  }

Dunque, a seconda del valore restituito dal metodo Trend(), viene eseguito il trailing stop per la posizione di acquisto o di vendita.

// depending on trend, shown by indicator, do various actions
switch (Trend())
  {
   // Up trend
   case 1: 
      // code of trailing stop for the buy position
      break;
   // Down trend
   case -1: 
      // code of trailing stop for the sell position
      break;
  }

Considera il suo lavoro sull'esempio della posizione di acquisto.

if(PositionSelect(m_symbol,1000))
  {   //--- select position. if succeeded, then position exists
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {//--- if position is buy

      //--- get Stop Loss value for the buy position
      sl=BuyStoploss(); 
      //--- find out allowed level of Stop Loss placement for the buy position
      double minimal=SymbolInfoDouble(m_symbol,SYMBOL_BID)-m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
      //--- value normalizing
      sl=NormalizeDouble(sl,m_digits); 
      //--- value normalizing
      minimal=NormalizeDouble(minimal,m_digits); 
      //--- if unable to place Stop Loss on level, obtained from indicator, 
      //    this Stop Loss will be placed on closest possible level
      sl=MathMin(sl,minimal); 
      //--- value of Stop Loss position
      double possl=PositionGetDouble(POSITION_SL); 
      //--- value normalizing
      possl=NormalizeDouble(possl,m_digits); 
      if(sl>possl)
        {//--- if new value of Stop Loss if bigger than current value of Stop Loss, 
         //    an attempt to move Stop Loss on a new level will be made
         //--- filling request structure
         m_request.sl=sl; 
         //--- filling request structure
         m_request.tp=PositionGetDouble(POSITION_TP); 
         //--- request
         OrderSend(m_request,m_result); 
         if(m_result.retcode!=TRADE_RETCODE_DONE)
           {//--- check request result
            //--- log error message
            printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode); 
            //--- unable to move Stop Loss, finishing
            return(false); 
           }
        }
     }
  }

Se la posizione può essere selezionata, il suo tipo è controllato. Se il tipo di posizione corrisponde al trend, allora, utilizzando il metodo BuyStoploss(), otteniamo il valore di Stop Loss richiesto (nella variabile sl). Quindi, determinare il livello consentito su cui è possibile impostare lo Stop Loss. Se il livello calcolato è più vicino di quanto consentito, regolare il valore della variabile sl. Quindi otteniamo il valore corrente della posizione di Stop Loss (nella variabile possl) e confrontiamo i valori delle variabili sl e possl. Se il nuovo valore di Stop Loss è migliore del valore corrente - modifica la posizione.

Prima della modifica compilare i campi sl e tp della struttura MqlTradeRequest. La variabile m_request.sl viene assegnata con il valore richiesto di Stop Loss, la variabile m_request.tp - con il valore esistente di Take Profit (lascialo invariato). Il resto dei campi viene compilato quando viene eseguito il metodo Init(). Dopo aver riempito la struttura, viene chiamata la funzione OrderSend().

Al termine del suo lavoro, viene verificato il valore della variabile m_result.retcode. Se il valore non è uguale a TRADE_RETCODE_DONE, allora per qualche motivo non è stato possibile eseguire l'azione richiesta dalla funzione OrderSend(). Allo stesso tempo, viene eseguito un messaggio di registro con il numero di errori e il completamento del metodo. Se la funzione OrderSend() viene conclusa con successo, allora ricorda il tempo della barra, su cui è stato effettuato l'ultimo lavoro del metodo DoStoploss(). In caso di errore, anche nella modalità per barra, al tick successivo verrà effettuato un tentativo per riprovare il metodo. I tentativi continueranno fino a quando completerà con successo il suo lavoro.

Di seguito, viene riportato l'intero codice del metodo DoStopLoss().

bool DoStoploss()
  {
//--- if trailing stop is turned off
   if(!m_onoff)
     {
      return(true);
     }
   datetime tm[1];
//--- get the time of last bar in per bar mode
   if(!m_eachtick)
     {
      //--- if unable to copy time, finish method, repeat on next tick 
      if(CopyTime(m_symbol,m_timeframe,0,1,tm)==-1)
        {
         return(false);
        }
      //--- if the bar time is equal to time of method's last execution - finish method
      if(tm[0]==m_lasttime)
        {
         return(true);
        }
     }
//--- get indicator values
   if(!Refresh())
     {
      return(false);
     }
   double sl;
//--- depending on trend, shown by indicator, do various actions
   switch(Trend())
     {
      //--- Up trend
      case 1:
         //--- select position. if succeeded, then position exists
         if(PositionSelect(m_symbol))
           {
            //--- if position is buy
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
              {
               //--- get Stop Loss value for the buy position
               sl=BuyStoploss();
               //--- find out allowed level of Stop Loss placement for the buy position
               double minimal=SymbolInfoDouble(m_symbol,SYMBOL_BID)-m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
               //--- value normalizing
               sl=NormalizeDouble(sl,m_digits);
               //--- value normalizing
               minimal=NormalizeDouble(minimal,m_digits);
               //--- if unable to place Stop Loss on level, obtained from indicator, 
               //    this Stop Loss will be placed on closest possible level
               sl=MathMin(sl,minimal);
               //--- value of Stop Loss position
               double possl=PositionGetDouble(POSITION_SL);
               //--- value normalizing
               possl=NormalizeDouble(possl,m_digits);
               //--- if new value of Stop Loss if bigger than current value of Stop Loss, 
               //    an attempt to move Stop Loss on a new level will be made
               if(sl>possl)
                 {
                  //--- filling request structure
                  m_request.sl=sl;
                  //--- filling request structure
                  m_request.tp=PositionGetDouble(POSITION_TP);
                  //--- request
                  OrderSend(m_request,m_result);
                  //--- check request result
                  if(m_result.retcode!=TRADE_RETCODE_DONE)
                    {
                     //--- log error message
                     printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode);
                     //--- unable to move Stop Loss, finishing
                     return(false);
                    }
                 }
              }
           }
         break;
         //--- Down trend
      case -1:
         if(PositionSelect(m_symbol))
           {
            if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
              {
               sl=SellStoploss();
               //--- adding spread, since Sell is closing by the Ask price
               sl+=(SymbolInfoDouble(m_symbol,SYMBOL_ASK)-SymbolInfoDouble(m_symbol,SYMBOL_BID));
               double minimal=SymbolInfoDouble(m_symbol,SYMBOL_ASK)+m_point*SymbolInfoInteger(m_symbol,SYMBOL_TRADE_STOPS_LEVEL);
               sl=NormalizeDouble(sl,m_digits);
               minimal=NormalizeDouble(minimal,m_digits);
               sl=MathMax(sl,minimal);
               double possl=PositionGetDouble(POSITION_SL);
               possl=NormalizeDouble(possl,m_digits);
               if(sl<possl || possl==0)
                 {
                  m_request.sl=sl;
                  m_request.tp=PositionGetDouble(POSITION_TP);
                  OrderSend(m_request,m_result);
                  if(m_result.retcode!=TRADE_RETCODE_DONE)
                    {
                     printf("Unable to move Stop Loss of position %s, error #%I64u",m_symbol,m_result.retcode);
                     return(false);
                    }
                 }
              }
           }
         break;
     }
//--- remember the time of method's last execution
   m_lasttime=tm[0];
   return(true);
  }

Notare le differenze del codice per le posizioni di acquisto e vendita. Per la posizione di vendita, il valore restituito da SellStoploss() aumenta del valore dello spread, perché la posizione di vendita viene chiusa dal prezzo Ask. Di conseguenza, il conteggio del livello minimo di Stop Loss per l'acquisto viene effettuato dal prezzo Bid, per la vendita - dal prezzo Ask.

Per ora, abbiamo terminato la creazione della classe base trailing stop. Procediamo con la creazione delle sottoclassi. 

2. Sottoclasse Trailing Stop per Indicatore SAR Parabolico

I metodi virtuali della classe CTrailingStop ti dicono già il contenuto di una sottoclasse, i metodi SetParameters(), Refresh(), Trend(), BuyStoploss(), SellStoploss() e il constructor della classi per impostare il nome del trailing stop. La classe sarà nominata CParabolicStop. Poiché questa classe è una sottoclasse di CTrailingStop, essa verrà menzionata nella sua dichiarazione.

class CParabolicStop: public CTrailingStop

Con questa dichiarazione, la chiamata ai metodi virtuali della classe CParabolicStop eseguirà i metodi ereditati della classe base.  

Analizziamo in dettaglio tutti i metodi della sottoclasse.

2.1. Metodo CParabolicStop()

Tale metodo ha il medesimo nome della classe stessa, questo metodo è chiamato constructor. Viene eseguito automaticamente quando viene caricata la classe, prima di qualsiasi altro metodo di classe. Nel metodo CParabolicStop() il nome del trailing stop è assegnato alla variabile m_typename. Questa variabile viene utilizzata per creare il nome e la didascalia del pulsante (nel metodo Init() della classe base).

void CParabolicStop()
  {
   m_typename="SAR"; // setting name of trailing stop type
  };

2.2. Metodo SetParameters()

Quando si chiama il metodo SetParameters(), esso accetta i parametri dell'indicatore e l'indicatore viene caricato con questi parametri. Se il parametro m_indicator è impostato sul metodo Init() della classe base, l'indicatore viene associato al grafico (funzione ChartIndicatorAdd()).

// Method of setting parameters and loading the indicator
bool SetParameters(double sarstep=0.02,double sarmaximum=0.2)
  {
   m_handle=iSAR(m_symbol,m_timeframe,sarstep,sarmaximum); // loading indicator
   if(m_handle==-1)
     {
      return(false); // if unable to load indicator, method returns false
     }
   if(m_indicator)
     {
      ChartIndicatorAdd(0,0,m_handle); // attach indicator to chart
     }
    
   return(true);
  }

2.3. Metodo Refresh()

Il metodo Refresh() ottiene un nuovo prezzo e aggiorna i valori degli indicatori. Nella sezione "protetta" della classe c'è l'array pricebuf per il valore del prezzo e l'array indbuf - per i valori dell'indicatore. Entrambi gli array hanno la dimensione di un elemento - dovrebbero essere solo un valore di prezzo e un valore di indicatore dalla barra che si sta formando o formata (a seconda del parametro m_shift, impostato durante l'inizializzazione della classe base).

// Method of getting indicator values
bool Refresh()
  {
   if(CopyBuffer(m_handle,0,m_shift,1,indbuf)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }
    
   if(CopyClose(m_symbol,m_timeframe,m_shift,1,pricebuf)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }    
   return(true); 
  }

2.4. Metodo Trend()

Il metodo Trend() controlla la posizione del prezzo rispetto alla linea dell'indicatore. Se il prezzo è al di sopra della linea, allora questa è la tendenza al rialzo e il metodo restituisce il valore 1. Se il prezzo è al di sotto della linea dell'indicatore, allora questa è la tendenza al ribasso e il metodo restituisce il valore -1. Non è escluso il caso (raro, ma possibile), in cui il prezzo è pari alla linea dell'indicatore. In questo caso, verrà restituito il valore 0.  

// Method of finding trend
int Trend()
  {
   if(pricebuf[0]>indbuf[0])
     { // price is higher than indicator line, up trend
      return(1);
     }
   if(pricebuf[0]<indbuf[0])
     { // price is lower than indicator line, down trend
      return(-1);
     }    
   return(0);
  }

2.5. Metodi BuyStoploss() e SellStoploss()

Poiché l'indicatore Parabolic SAR ha una sola linea, entrambi i metodi sono identici. Restituiscono il valore ottenuto dal metodo Refresh().

// Method of finding out Stop Loss level for buy
virtual double BuyStoploss()
  {
   return(indbuf[0]);
  };
// Method of finding out Stop Loss level for sell
virtual double SellStoploss()
  {
   return(indbuf[0]);
  };

Per ora, il trailing stop è pronto. Ha ancora una sola classe, ma può essere già utilizzato. Salvalo come file di inclusione separato nella cartella .\MQL5\Include con il nome Sample_TrailingStop.mqh (il file è allegato all'articolo). 

3. Aggiungere il Trailing Stop per Parabolic nell’Expert

Proviamo ad aggiungere il trailing stop in qualche expert, come My_First_EA dall’articolo Guida Step-By-Step alla scrittura di un Expert Advisor in MQL5 per Principianti.

3.1. Apri l'expert My_First_EA in MetaEditor e salvalo come My_First_EA_SARTrailing.

3.2. Includi il file trailing stop. Nella parte superiore del codice dell’expert (preferibilmente prima della dichiarazione delle variabili esterne) aggiungi la riga: 

#include <Sample_TrailingStop.mqh> // include Trailing Stop class

3.3. Dopo che le variabili esterne crea un'istanza della classe CParabolicStop, nominata Trailing.

CParabolicStop Trailing; // create class instance 

 3.4. Nella funzione OnInit() inizializza la classe e imposta i suoi parametri. Innanzitutto, dichiara le variabili esterne con i parametri dell'indicatore:

input double TrailingSARStep=0.02;
input double TrailingSARMaximum=0.2;

Quindi, aggiungi il codice alla funzione OnInit().

Trailing.Init(_Symbol,PERIOD_CURRENT,true,true,false); // Initialize (set basic parameters)
if(!trailing.setparameters(TrailingSARStep,TrailingSARMaximum))
  { // Set parameters of used trailing stop type
   Alert("trailing error");
   return(-1);
  } 
Trailing.StartTimer(); // Start timer
Trailing.On();         // Turn on

3.5. Nel codice dell’expert, trova la funzione OnTimer(). La funzione OnTimer() non viene utilizzata in My_First_EA, quindi aggiungila e aggiungi la chiamata di Refresh(). 

void OnTimer()
  {
   Trailing.Refresh();
  }

3.6. Nella parte superiore della funzione OnTick(), aggiungi la chiamata del metodo DoStoploss().

3.7. Compila l’expert e testalo. I risultati del test dell'expert sono mostrati nella Figura 7 (senza trailing stop) e nella Figura 8 (con trailing stop).

Figura 7. Risultati del test dell'Expert senza Trailing Stop.

Figura 7. Risultati del test dell'Expert senza Trailing Stop.   

Figura 8. Risultati del test dell'Expert con Trailing Stop.

Figura 8. Risultati del test dell'Expert con Trailing Stop. 

L'efficacia dell'utilizzo del trailing stop è ovvia.

Il file My_First_EA_SARTrailing.mq5 è allegato all'articolo.

4. Sottoclasse Trailing Stop per NRTR

L'indicatore NRTR(Nick Rypock Trailing Reverse) con il suo nome e aspetto (Figura 9) rende interessante provare a creare un trailing stop su di esso.

Figura 9. Indicatore NRTR.

Figura 9. Indicatore NRTR.

L'indicatore disegna la base line (la linea di supporto o resistenza) e la target line. Quando il prezzo supera la target line, la base line viene trasferita nella direzione del movimento del prezzo, le oscillazioni minori del prezzo vengono ignorate. Quando il prezzo interseca la base line, viene considerato un cambiamento di tendenza, modificando così la posizione della base line e della target line rispetto al prezzo. La linea di supporto e la target line al rialzo sono colorate con il blu, mentre nella tendenza al ribasso - con il rosso.

Crea un altro trailing stop, ora per l'indicatore NRTR.

Dichiarare un'altra classe CNRTRStop inclusa nella classe base CNRTRStop. 

class CNRTRStop: public CTrailingStop

Il Trailing Stop per la sottoclasse NRTR avrà esattamente lo stesso insieme di metodi del Trailing Stop per Parabolic, eccetto il constructor- ora sarà chiamato CNRTRStop().  

4.1. Metodo CNRTRStop()

Ora nel constructor della classe, la variabile m_typename viene assegnata con il valore NRTR, in base all'indicatore utilizzato.

void CNRTRStop()
  {
   m_typename="NRTR"; // setting name of trailing stop type
  };

4.2. Metodo SetParameters()

Quando si chiama il metodo SetParameters(), esso accetta i parametri dell'indicatore e viene caricato. Quindi, a seconda dei parametri di base del trailing stop, l'indicatore sarà associato al grafico.

// Method of setting parameters and loading the indicator
bool SetParameters(int period,double k)
  {
   m_handle=iCustom(m_symbol,m_timeframe,"NRTR",period,k); // loading indicator
   if(m_handle==-1)
     { // if unable to load indicator, method returns false
      return(false); 
     }
   if(m_indicator)
     {
       
      ChartIndicatorAdd(0,0,m_handle); // attach indicator to chart
     }
   return(true);
  }

4.3. Metodo Refresh()

Il metodo Refresh() copia due buffer dell'indicatore NRTR: buffer della linea di supporto e buffer della linea di resistenza.  

 // Method of getting indicator values
bool Refresh()
  {
   if(CopyBuffer(m_handle,0,m_shift,1,sup)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }
    
   if(CopyBuffer(m_handle,1,m_shift,1,res)==-1)
     {
      return(false); // if unable to copy value to array, return false
     }
    
   return(true);
  }

Nella sezione "protetta" della classe sono presenti due array dichiarati: double sup[] e double res[].

protected:
double sup[1]; // value of support level
double res[1]; // value of resistance level

4.4. Metodo Trend()

Il metodo Trend() controlla quale delle linee esiste attualmente. Se è la linea di supporto, significa che l'indicatore mostra la tendenza al rialzo. Il metodo stesso restituisce il valore 1. Se è la linea di resistenza, allora il metodo restituisce il valore -1. 

// Method of finding trend
int Trend()
  {
   if(sup[0]!=0)
     { // there is support line, then it is up trend
      return(1);
     }
   if(res[0]!=0)
     { // there is resistance line, then it is down trend
      return(-1);
     }
    
   return(0);
  }

4.5. Metodo BuyStoploss()

Il metodo BuyStoploss() restituisce il valore della linea di supporto. 

// Method of finding out Stop Loss level for buy
double BuyStoploss()
  {
   return(sup[0]);
  }

4.6. Metodo SellStoploss()

Il metodo SellStoploss() restituisce il valore della linea di resistenza.  

// Method of finding out Stop Loss level for sell
double SellStoploss()
  {
   return(res[0]);
  }

Ora la classe trailing stop è completa. 

5. Aggiungere il Trailing Stop per NRTR nell’Expert

Proprio come con il trailing stop per Parabolic, aggiungiamo il trailing stop My_First_EA per NRTR nell’expert.

5.1. Apri l'expert My_First_EA_SARTrailing in MetaEditor e salvalo come My_First_EA_NRTRTrailing.

5.2. Sostituire i parametri esterni del trailing stop per Parabolic con i parametri del trailing stop per NRTR.

input int TrailingNRTRPeriod = 40;
input double TrailingNRTRK   =  2;

5.3. Invece di creare un'istanza della classe CParabolicStop, creare un'istanza della classe CNRTRStop. Il codice si trova dopo le variabili esterne. 

CNRTRStop Trailing; // create class instance 

5.4. Nella funzione OnInit() sostituire i parametri della chiamata al metodo SetParameters() con i parametri NRTR.

Trailing.SetParameters(TrailingNRTRPeriod,TrailingNRTRK)

5.5. Compila l’expert e testalo. 

Figura 10. Risultati del test dell'Expert con Trailing Stop per NRTR.

Figura 10. Risultati del test dell'Expert con Trailing Stop per NRTR.

I risultati di lavoro dell'Expert Advisor (Figura 10) con una strategia di trailing stop rispetto al lavoro di un expert senza di esso (Fig. 7) sono pressoché invariati. L'uso del trailing stop per Parabolic si è dimostrato più efficace per questo expert. Si può concludere che l'arsenale di un certo numero di trailing stop può essere molto utile quando si sviluppano expert - per fare esperimenti e selezionare il tipo di trailing stop più adatto.  

Il file My_First_EA_NRTRTrailing.mq5 è allegato all'articolo.

6. Expert-Assistant

Quando è stata creata una classe base di trailing stop, si intendeva controllare l'attivazione/disattivazione del trailing stop tramite il pulsante. Creiamo un expert-assistant per seguire le posizioni sui vari simboli con diverse tipologie di trailing stop. L'expert non aprirà posizioni, ma seguirà solo quelle aperte.

6.1. In MetaEditor crea un nuovo expert chiamato Sample_TrailingStop.

6.2. Includere il file Sample_TrailingStop.mqh. 

#include <Sample_TrailingStop.mqh> // include Trailing Stop class

6.3. Dichiarare i parametri esterni per gli indicatori.

input double SARStep=0.02;     // Step of Parabolic
input double SARMaximum=0.02;  // Maximum of Parabolic
input int NRTRPeriod=40;       // NRTR period
input double NRTRK=2;          // NRTR factor

6.4. Dichiara un array di simboli su cui l'expert potrà lavorare.

string Symbols[]={"EURUSD","GBPUSD","USDCHF","USDJPY"};

6.5. Dichiara gli array per caricare le classi.

CParabolicStop *SARTrailing[];
CNRTRStop *NRTRTrailing[];

6.6. Nella funzione OnInit() ridimensionare gli array per caricare le classi, in base alla dimensione dell'array Symbols. 

ArrayResize(SARTrailing,ArraySize(Symbols));  // resize according to number of used symbols
ArrayResize(NRTRTrailing,ArraySize(Symbols)); // resize according to number of used symbols 

6.7. Nel ciclo per ogni elemento dell'istanza della classe di caricamento dell'array.

for(int i=0;i<ArraySize(Symbols);i++)
  { // for all symbols
   SARTrailing[i]=new CParabolicStop(); // create CParabolicStop class instance
   SARTrailing[i].Init(Symbols[i],PERIOD_CURRENT,false,true,true,5,15+i*17,Silver,Blue);    // initialization of CParabolicStop class instance 
   if(!SARTrailing[i].SetParameters(SARStep,SARMaximum))
     { // setting parameters of CParabolicStop class instance 
      Alert("trailing error");
      return(-1);
     }
   SARTrailing[i].StartTimer();         // start timer
//----
   NRTRTrailing[i]=new CNRTRStop();     // create CNRTRStop class instance
   NRTRTrailing[i].Init(Symbols[i],PERIOD_CURRENT,false,true,true,127,15+i*17,Silver,Blue); // initialization of CNRTRStop class instance
   if(!NRTRTrailing[i].SetParameters(NRTRPeriod,NRTRK))
     { // setting parameters of CNRTRcStop class instance 
      Alert("trailing error");
      return(-1);
     }
   NRTRTrailing[i].StartTimer();        // start timer 
  }

Nota: quando si chiamano i pulsanti dei metodi Init() vengono calcolate le coordinate. Sulla sinistra, ci saranno i pulsanti di accensione del trailing stop per Parabolic, sulla destra - per NRTR.  

6.8. La funzione Int OnTick() aggiunge la chiamata del metodo DoStoploss() per ogni istanza di trailing stop.

for(int i=0;i<ArraySize(Symbols);i++)
  {
   SARTrailing[i].DoStoploss();
   NRTRTrailing[i].DoStoploss();
  }

6.9. Aggiungi la gestione degli eventi del grafico. 

void OnChartEvent(const int         id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam 
                  )
  {
   for(int i=0;i<ArraySize(Symbols);i++)
     {
      SARTrailing[i].EventHandle(id,lparam,dparam,sparam);
      NRTRTrailing[i].EventHandle(id,lparam,dparam,sparam);
     }
    
  }

6.10. Nella funzione Deinit() reinizializza tutte le istanze di classe ed eliminale.

for(int i=0;i<ArraySize(Symbols);i++)
  {
   SARTrailing[i].Deinit(); 
   NRTRTrailing[i].Deinit();
   delete(SARTrailing[i]);  // delete object
   delete(NRTRTrailing[i]); // delete object
  }

Compila, associa l’expert al grafico. Gli indicatori e pulsanti vengono visualizzati sul grafico (Figura 11) - l’Expert Advisor è pronto per operare.  

Figura 11. Pulsanti e Indicatori sul Grafico dopo l'Avvio di Sample_TrailingStop.

Figura 11. Pulsanti e Indicatori sul Grafico dopo Aver avviato il Sample_TrailingStop. 

Basta premere il pulsante per seguire la posizione corrispondente quando viene aperto.

Il file Sample_TrailingStop.mq5 è allegato all'articolo.

Conclusione

Rivediamo l'ordine di utilizzo della classe CTrailingStop durante la creazione di un sistema di trading meccanico:

1. Includere il file Sample_TrailingStop.mqh.

2. Dichiarare variabili esterne con parametri indicatori del trailing stop utilizzato.

3. Creare l'istanza della classe.

4. Aggiungere la chiamata dei metodi Init(), SetParameters(), StartTimer() e On() dalla funzione OnInit().

5. Aggiungi la chiamata del metodo Refresh() dalla funzione OnTimer().

6. Aggiungi la chiamata del metodo DoStopLoss() dalla funzione OnTick().

7. Aggiungi la chiamata del metodo Deinit() dalla funzione OnDeinit(). 


Sette passaggi, meno di 5 minuti ed il tuo Expert Advisor ha una funzione di trailing stop!