English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Creare un Expert Advisor interattivo semiautomatico drag-and-drop basato su rischio predefinito e rapporto R/R

Creare un Expert Advisor interattivo semiautomatico drag-and-drop basato su rischio predefinito e rapporto R/R

MetaTrader 5Esempi | 17 dicembre 2021, 14:40
136 0
investeo
investeo

Introduzione

Alcuni trader eseguono automaticamente tutte le loro operazioni, mentre alcuni combinano operazioni automatiche e manuali in base all'output dei diversi indicatori. Facendo parte di quest'ultimo gruppo, avevo bisogno di uno strumento interattivo per valutare dinamicamente il rischio e i livelli di prezzo del rendimento direttamente dal grafico. ???

Una volta dichiarato il rischio massimo sul mio patrimonio, volevo calcolare in tempo reale i parametri basati sul livello di stop-loss che avevo inserito sul grafico e avevo bisogno di fare trading direttamente dall'EA basato sui livelli SL e TP calcolati.

Questo articolo presenterà un modo per implementare un Expert Advisor interattivo semiautomatico con rischio azionario predefinito e rapporto R/R. I parametri di rischio, R/R e dimensione del lotto dell’Expert Advisor possono essere modificati durante l’esecuzione sul pannello EA. 


1. Requisiti

I requisiti per l'EA erano i seguenti:

  • capacità di predefinire il livello di rischio all'avvio e di modificarlo durante l’esecuzione per vedere il modo in cui avrebbe influito sulla dimensione della posizione
  • capacità di predefinire il rapporto rischio/rendimento e di modificarlo durante l’esecuzione
  • capacità di calcolare in tempo reale la dimensione massima del lotto per un dato rischio e il livello di stop-loss
  • capacità di modificare la dimensione del lotto in fase di esecuzione per vedere come avrebbe influito sul rischio e sul rendimento azionario
  • capacità di eseguire ordini di mercato di acquisto/vendita direttamente dall’EA
  • interfaccia drag and drop per impostare lo stop-loss e visualizzare il livello di prezzo del rischio predefinito per il livello di rendimento 


2. Design

Per via dei requisiti di visualizzazione e modifica dei parametri dell'EA durante l’esecuzione, ho deciso di utilizzare le classi CChartObject e i suoi discendenti per visualizzare la GUI nella finestra del grafico e gestire gli eventi in arrivo per l'interazione dell'utente. Pertanto, l'EA aveva bisogno di un'interfaccia utente con etichette, pulsanti e campi di modifica.

Inizialmente volevo utilizzare l'oggetto CChartObjectPanel per raggruppare gli altri oggetti sul pannello, ma ho deciso di provare un approccio diverso. Ho progettato una classe che contiene etichette, campi di modifica e pulsanti che mostra su un’immagine di sfondo. L'immagine di sfondo dell'interfaccia è stata realizzata utilizzando il software GIMP. Gli oggetti generati da MQL5 consistono in campi di modifica, etichette rosse aggiornate in tempo reale e pulsanti.

Io ho solamente inserito gli oggetti etichetta sul grafico, registrato la loro posizione e costruito la classe CRRDialog che gestisce tutte le funzioni di visualizzazione dell'output calcolato, ricezione dei parametri dei campi CChartObjectEdit e registrazione dello stato dei pulsanti. I rettangoli a colori di rischio e rendimento sono oggetti della classe CChartObjectRectangle, mentre il puntatore di stop loss trascinabile è un oggetto bitmap della classe CChartObjectBitmap.


 

Figura 1. Schermata Visual EA

Figura 1. Schermata Visual EA

 


3. Implementazione della classe di dialogo EA

La classe CRRDialog gestisce l'intera interfaccia utente dell'EA. Contiene un numero di variabili che sono visualizzate, oggetti che vengono utilizzati per mostrare le variabili e metodi per ottenere/impostare i loro valori e aggiornare la finestra di dialogo.

Sto usando l'oggetto CChartObjectBmpLabel per lo sfondo, gli oggetti CChartObjectEdit per i campi di modifica, gli oggetti CChartObjectLabel per la visualizzazione delle etichette e gli oggetti CChartObjectButton per i pulsanti: 

class CRRDialog
  {
private:

   int               m_baseX;
   int               m_baseY;
   int               m_fontSize;
   
   string            m_font;
   string            m_dialogName;
   string            m_bgFileName;

   double            m_RRRatio;
   double            m_riskPercent;
   double            m_orderLots;
   double            m_SL;
   double            m_TP;
   double            m_maxAllowedLots;
   double            m_maxTicksLoss;
   double            m_orderEquityRisk;
   double            m_orderEquityReward;
   ENUM_ORDER_TYPE   m_orderType;

   CChartObjectBmpLabel m_bgDialog;

   CChartObjectEdit  m_riskRatioEdit;
   CChartObjectEdit  m_riskValueEdit;
   CChartObjectEdit  m_orderLotsEdit;

   CChartObjectLabel m_symbolNameLabel;
   CChartObjectLabel m_tickSizeLabel;
   CChartObjectLabel m_maxEquityLossLabel;
   CChartObjectLabel m_equityLabel;
   CChartObjectLabel m_profitValueLabel;
   CChartObjectLabel m_askLabel;
   CChartObjectLabel m_bidLabel;
   CChartObjectLabel m_tpLabel;
   CChartObjectLabel m_slLabel;
   CChartObjectLabel m_maxAllowedLotsLabel;
   CChartObjectLabel m_maxTicksLossLabel;
   CChartObjectLabel m_orderEquityRiskLabel;
   CChartObjectLabel m_orderEquityRewardLabel;
   CChartObjectLabel m_orderTypeLabel;

   CChartObjectButton m_switchOrderTypeButton;
   CChartObjectButton m_placeOrderButton;
   CChartObjectButton m_quitEAButton;

public:

   void              CRRDialog(); // CRRDialog constructor
   void             ~CRRDialog(); // CRRDialog destructor

   bool              CreateCRRDialog(int topX,int leftY);
   int               DeleteCRRDialog();
   void              Refresh();
   void              SetRRRatio(double RRRatio);
   void              SetRiskPercent(double riskPercent);
   double            GetRiskPercent();
   double            GetRRRRatio();
   void              SetSL(double sl);
   void              SetTP(double tp);
   double            GetSL();
   double            GetTP();
   void              SetMaxAllowedLots(double lots);
   void              SetMaxTicksLoss(double ticks);
   void              SetOrderType(ENUM_ORDER_TYPE);
   void              SwitchOrderType();
   void              ResetButtons();
   ENUM_ORDER_TYPE   GetOrderType();
   void              SetOrderLots(double orderLots);
   double            GetOrderLots();
   void              SetOrderEquityRisk(double equityRisk);
   void              SetOrderEquityReward(double equityReward);
  };

Dato che i metodi get/set delle variabili sono semplici, mi concentrerò sui metodi CreateCRRDialog() e Refresh(). Il metodo CreateCRRDialog() inizializza l'immagine di sfondo, le etichette, i pulsanti e i campi di modifica.

Per inizializzare le etichette e i campi di modifica utilizzo: il metodo Create() con parametri di coordinate per localizzare l'oggetto sul grafico, i metodi Font() e FontSize() per impostare il carattere e il metodo Description() per inserire il testo sull'etichetta.

Per i pulsanti: I parametri aggiuntivi del metodo Create() specificano la dimensione del pulsante mentre il metodo BackColor() specifica il colore di sfondo del pulsante. 

bool CRRDialog::CreateCRRDialog(int topX,int leftY)
  {
   bool isCreated=false;

   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_baseX = topX;
   m_baseY = leftY;

   m_bgDialog.Create(0, m_dialogName, 0, topX, leftY);
   m_bgDialog.BmpFileOn(m_bgFileName);

   m_symbolNameLabel.Create(0, "symbolNameLabel", 0, m_baseX + 120, m_baseY + 40);
   m_symbolNameLabel.Font("Verdana");
   m_symbolNameLabel.FontSize(8);
   m_symbolNameLabel.Description(Symbol());

   m_tickSizeLabel.Create(0, "tickSizeLabel", 0, m_baseX + 120, m_baseY + 57);
   m_tickSizeLabel.Font("Verdana");
   m_tickSizeLabel.FontSize(8);
   m_tickSizeLabel.Description(DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits()));

   m_riskRatioEdit.Create(0, "riskRatioEdit", 0, m_baseX + 120, m_baseY + 72, 35, 15);
   m_riskRatioEdit.Font("Verdana");
   m_riskRatioEdit.FontSize(8);
   m_riskRatioEdit.Description(DoubleToString(m_RRRatio, 2));

   m_riskValueEdit.Create(0, "riskValueEdit", 0, m_baseX + 120, m_baseY + 90, 35, 15);
   m_riskValueEdit.Font("Verdana");
   m_riskValueEdit.FontSize(8);
   m_riskValueEdit.Description(DoubleToString(m_riskPercent, 2));

   m_equityLabel.Create(0, "equityLabel", 0, m_baseX + 120, m_baseY + 107);
   m_equityLabel.Font("Verdana");
   m_equityLabel.FontSize(8);
   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));

   m_maxEquityLossLabel.Create(0, "maxEquityLossLabel", 0, m_baseX + 120, m_baseY + 122);
   m_maxEquityLossLabel.Font("Verdana");
   m_maxEquityLossLabel.FontSize(8);
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*m_riskPercent/100.0,2));

   m_askLabel.Create(0, "askLabel", 0, m_baseX + 120, m_baseY + 145);
   m_askLabel.Font("Verdana");
   m_askLabel.FontSize(8);
   m_askLabel.Description("");

   m_bidLabel.Create(0, "bidLabel", 0, m_baseX + 120, m_baseY + 160);
   m_bidLabel.Font("Verdana");
   m_bidLabel.FontSize(8);
   m_bidLabel.Description("");

   m_slLabel.Create(0, "slLabel", 0, m_baseX + 120, m_baseY + 176);
   m_slLabel.Font("Verdana");
   m_slLabel.FontSize(8);
   m_slLabel.Description("");

   m_tpLabel.Create(0, "tpLabel", 0, m_baseX + 120, m_baseY + 191);
   m_tpLabel.Font("Verdana");
   m_tpLabel.FontSize(8);
   m_tpLabel.Description("");

   m_maxAllowedLotsLabel.Create(0, "maxAllowedLotsLabel", 0, m_baseX + 120, m_baseY + 208);
   m_maxAllowedLotsLabel.Font("Verdana");
   m_maxAllowedLotsLabel.FontSize(8);
   m_maxAllowedLotsLabel.Description("");

   m_maxTicksLossLabel.Create(0, "maxTicksLossLabel", 0, m_baseX + 120, m_baseY + 223);
   m_maxTicksLossLabel.Font("Verdana");
   m_maxTicksLossLabel.FontSize(8);
   m_maxTicksLossLabel.Description("");

   m_orderLotsEdit.Create(0, "orderLotsEdit", 0, m_baseX + 120, m_baseY + 238, 35, 15);
   m_orderLotsEdit.Font("Verdana");
   m_orderLotsEdit.FontSize(8);
   m_orderLotsEdit.Description("");

   m_orderEquityRiskLabel.Create(0, "orderEquityRiskLabel", 0, m_baseX + 120, m_baseY + 255);
   m_orderEquityRiskLabel.Font("Verdana");
   m_orderEquityRiskLabel.FontSize(8);
   m_orderEquityRiskLabel.Description("");

   m_orderEquityRewardLabel.Create(0, "orderEquityRewardLabel", 0, m_baseX + 120, m_baseY + 270);
   m_orderEquityRewardLabel.Font("Verdana");
   m_orderEquityRewardLabel.FontSize(8);
   m_orderEquityRewardLabel.Description("");

   m_switchOrderTypeButton.Create(0, "switchOrderTypeButton", 0, m_baseX + 20, m_baseY + 314, 160, 20);
   m_switchOrderTypeButton.Font("Verdana");
   m_switchOrderTypeButton.FontSize(8);
   m_switchOrderTypeButton.BackColor(LightBlue);

   m_placeOrderButton.Create(0, "placeOrderButton", 0, m_baseX + 20, m_baseY + 334, 160, 20);
   m_placeOrderButton.Font("Verdana");
   m_placeOrderButton.FontSize(8);
   m_placeOrderButton.BackColor(LightBlue);
   m_placeOrderButton.Description("Place Market Order");

   m_quitEAButton.Create(0, "quitEAButton", 0, m_baseX + 20, m_baseY + 354, 160, 20);
   m_quitEAButton.Font("Verdana");
   m_quitEAButton.FontSize(8);
   m_quitEAButton.BackColor(LightBlue);
   m_quitEAButton.Description("Quit");

   return isCreated;
  }

Il metodo Refresh() aggiorna tutte le etichette e la descrizione dei pulsanti con le variabili CRRDialog e i livelli bid/ask correnti, l'equità del conto e i valori di rischio azionario: 

void CRRDialog::Refresh()
  {
   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*
                                         StringToDouble(m_riskValueEdit.Description())/100.0,2));
   m_askLabel.Description(DoubleToString(current_tick.ask, Digits()));
   m_bidLabel.Description(DoubleToString(current_tick.bid, Digits()));
   m_slLabel.Description(DoubleToString(m_SL, Digits()));
   m_tpLabel.Description(DoubleToString(m_TP, Digits()));
   m_maxAllowedLotsLabel.Description(DoubleToString(m_maxAllowedLots,2));
   m_maxTicksLossLabel.Description(DoubleToString(m_maxTicksLoss,0));
   m_orderEquityRiskLabel.Description(DoubleToString(m_orderEquityRisk,2));
   m_orderEquityRewardLabel.Description(DoubleToString(m_orderEquityReward,2));

   if(m_orderType==ORDER_TYPE_BUY) m_switchOrderTypeButton.Description("Order Type: BUY");
   else if(m_orderType==ORDER_TYPE_SELL) m_switchOrderTypeButton.Description("Order Type: SELL");
  }


4. Eventi del grafico

Poiché l’EA è progettato per essere interattivo, dovrà gestire gli eventi del grafico.

Gli eventi che vengono gestiti includono:

  • il trascinamento del puntatore S/L (oggetto SL_arrow della classe CChartObjectBitmap) sul grafico. Questo consentirà di raccogliere il livello S/L e calcolare il livello T/P basato sul rapporto R/R
  • pulsante per il cambio del tipo di ordine (acquisto/vendita)
  • pulsante per l’opzione "inserisci ordine di mercato"
  • la modifica dei campi rischio, R/R e lotto ordine
  • la chiusura dell’EA dopo aver premuto il pulsante 'Esci'

Gli eventi gestiti sono CHARTEVENT_OBJECT_CLICK per la selezione del puntatore e i pulsanti, CHARTEVENT_OBJECT_DRAG per il trascinamento del puntatore S/L e CHARTEVENT_OBJECT_ENDEDIT dopo che i campi di modifica vengono aggiornati dal trader.

Inizialmente, l'implementazione della funzione OnChartEvent() ha richiesto alcune pagine di codice, ma ho deciso di dividere in diversi gestori di eventi così da convertire la funzione OnChartEvent() in un formato leggibile dall'uomo: 

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Check the event by pressing a mouse button
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      string clickedChartObject=sparam;

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

      if(clickedChartObject==switchOrderTypeButtonID)
        {
         EA_switchOrderType();
        };

      if(clickedChartObject==placeOrderButtonID)
        {
         EA_placeOrder();
        }

      if(clickedChartObject==quitEAButtonID) ExpertRemove();

      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      // BUY 
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         EA_dragBuyHandle();
        };

      // SELL
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         EA_dragSellHandle();
        };
      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      if((sparam==riskRatioEditID || sparam==riskValueEditID || sparam==orderLotsEditID) && orderPlaced==false)
        {
         EA_editParamsUpdate();
        }
     }
  }

L'implementazione dei gestori di eventi verrà descritta più dettagliatamente nelle sezioni successive. Degno di nota è il trucco che ho usato per selezionare l'oggetto SL_arrow. Normalmente, per selezionare un oggetto sul grafico è necessario cliccare due volte su di esso. Tuttavia, lo si può selezionare cliccando una volta e ricorrendo al metodo Selected() dell'oggetto CChartObject o il suo discendente nella funzione OnChartEvent() all'interno del gestore eventi CHARTEVENT_OBJECT_CLICK:

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

L’oggetto viene selezionato o deselezionato a seconda del suo stato precedente dopo un clic. 


5. Classe di gestione estesa del denaro basata su CMoneyFixedRisk

Prima di poter descrivere i gestori di ChartEvent, occorre parlare della classe di gestione del denaro.

Per la gestione del denaro ho riutilizzato la classe CMoneyFixedRisk fornita da MetaQuotes e implementato la classe CMoneyFixedRiskExt.

I metodi originali della classe CMoneyFixedRisk restituiscono gli importi dei lotti dell'ordine consentiti per un dato prezzo, il livello di stop-loss e il rischio azionario tra la dimensione minima e massima del lotto consentita dal broker. Ho cambiato i metodi CheckOpenLong() e CheckOpenShort() per ritornare alla dimensione 0.0 del lotto qualora i requisiti di rischio non fossero soddisfatti e li ho estesi con quattro metodi: GetMaxSLPossible(), CalcMaxTicksLoss(), CalcOrderEquityRisk() e CalcOrderEquityReward():

class CMoneyFixedRiskExt : public CExpertMoney
  {
public:
   //---
   virtual double    CheckOpenLong(double price,double sl);
   virtual double    CheckOpenShort(double price,double sl);
   
   double GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType);
   double CalcMaxTicksLoss();
   double CalcOrderEquityRisk(double price, double sl, double lots);
   double CalcOrderEquityReward(double price, double sl, double lots, double rrratio);
  };

Il metodo GetMaxSLPossible() calcola il valore massimo del prezzo di stop-loss per un dato rischio azionario e la dimensione minima consentita per il trading.

Per esempio, se il saldo del conto è 10.000 nella valuta di base del conto e il rischio è del 2%, possiamo mettere a rischio un massimo di 200 nella valuta del conto. Se la dimensione minima del lotto è 0,1, questo metodo restituisce il livello di prezzo per l'ordine ORDER_TYPE_BUY o ORDER_TYPE_SELL che soddisferà il valore del rischio azionario per la posizione 0,1. Questo ci aiuta a stimare qual è il livello massimo di stop-loss che possiamo permetterci per le dimensioni minime del lotto. S tratta di un livello di prezzo che non possiamo superare per un dato livello di rischio azionario. 

double CMoneyFixedRiskExt::GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType)
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   double orderTypeMultiplier;
   
   if(m_symbol==NULL) return(0.0);
   
   switch (orderType)
   {
   case ORDER_TYPE_SELL: orderTypeMultiplier = -1.0; break;
   case ORDER_TYPE_BUY: orderTypeMultiplier = 1.0; break;
   default: orderTypeMultiplier = 0.0;
   }
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // max loss 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
 
   return (price - maxTicksLoss*m_symbol.TickSize()*orderTypeMultiplier);
}

Il metodo CalcMaxTickLoss() restituisce il numero massimo di tick che possiamo permetterci di perdere per un dato rischio e la dimensione minima del lotto consentita.

In un primo momento viene calcolata la perdita massima del capitale come percentuale del saldo corrente, quindi viene calcolata la perdita del valore del tick per la variazione di un tick per la dimensione minima consentita del lotto per un dato simbolo. Dopodiché, la perdita di capitale massima viene divisa per la perdita di valore del tick e il risultato ottenuto viene arrotondato al valore intero con la funzione MathFloor():

double CMoneyFixedRiskExt::CalcMaxTicksLoss()
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   
   if(m_symbol==NULL) return(0.0);
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // max loss 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
   
   return (maxTicksLoss);
}

Il metodo CalcOrderEquityRisk() restituisce il rischio azionario per un dato prezzo, il livello di stop loss e la quantità di lotti. Viene calcolato moltiplicando il valore della perdita di tick per il numero di lotti e il prezzo, poi si moltiplica per la differenza tra il prezzo corrente e il livello di stop-loss:

double CMoneyFixedRiskExt::CalcOrderEquityRisk(double price,double sl, double lots)
{
   double equityRisk;
   
   equityRisk = lots*m_symbol.TickValueLoss()*(MathAbs(price-sl)/m_symbol.TickSize()); 
   
   if (dbg) Print("calcEquityRisk: lots = " + DoubleToString(lots) +
                 " TickValueLoss = " + DoubleToString(m_symbol.TickValueLoss()) +
                 " risk = " + DoubleToString(equityRisk));
   
   return equityRisk;
}

 Il metodo CalcOrderEquityReward() è analogo al metodo CalcOrderEquityRisk(), ma utilizza il metodo TickValueProfit() invece del metodo TickValueLoss() e il risultato viene moltiplicato per un dato rapporto di rischio/rendimento:  

double CMoneyFixedRiskExt::CalcOrderEquityReward(double price,double sl, double lots, double rrratio)
{
   double equityReward; 
   equityReward = lots*m_symbol.TickValueProfit()*(MathAbs(price-sl)/m_symbol.TickSize())*rrratio; 
   
   if (dbg) Print("calcEquityReward: lots = " + DoubleToString(lots) + 
                   " TickValueProfit = " + DoubleToString(m_symbol.TickValueProfit()) +
                 " reward = " + DoubleToString(equityReward));
   return equityReward;
}

Questi metodi sono sufficienti per calcolare i livelli massimi di stop-loss e restituire il rischio e il rendimento azionario in tempo reale. Il metodo CalcMaxTickLoss() viene utilizzato per correggere il disegno del rettangolo di rischio: se il trader vuole inserire un'operazione che attraversa il limite del numero di tick che può permettersi di perdere, il rettangolo viene disegnato solo tenendo conto del numero massimo di tick che può perdere.

Osservarlo direttamente sul grafico è di gran lunga più semplice. Potrai vederlo nella demo alla fine dell'articolo.


6. Implementazione dei gestori di eventi del grafico

Il gestore EA_switchOrderType() viene attivato dopo aver ricevuto l'evento CHARTEVENT_OBJECT_CLICK sull'oggetto m_switchOrderTypeButton . Cambia il tipo di ordine tra ORDER_TYPE_BUY e ORDER_TYPE_SELL, ripristina lo stato dei pulsanti, le variabili della finestra di dialogo ed elimina gli oggetti rettangolo di rischio e rendimento sul grafico:

void EA_switchOrderType()
  {
   symbolInfo.RefreshRates();

   visualRRDialog.SwitchOrderType();
   visualRRDialog.ResetButtons();
   visualRRDialog.SetSL(0.0);
   visualRRDialog.SetTP(0.0);
   visualRRDialog.SetMaxAllowedLots(0.0);
   visualRRDialog.SetOrderLots(0.0);
   visualRRDialog.SetMaxTicksLoss(0);
   visualRRDialog.SetOrderEquityRisk(0.0);
   visualRRDialog.SetOrderEquityReward(0.0);

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
   SL_arrow.SetInteger(OBJPROP_TIME,0,TimeCurrent());

   rectReward.Delete();
   rectRisk.Delete();

   visualRRDialog.Refresh();

  }

Il gestore EA_dragBuyHandle() viene attivato dopo che l'oggetto SL_arrow è stato trascinato e rilasciato sul grafico. All'inizio leggerà il tempo del punto di caduta dell'oggetto SL_arrow e i parametri del prezzo dal grafico e imposterà il livello del prezzo come un ipotetico stop-loss per il nostro trading.

Quindi calcolerà quanti lotti possiamo aprire per un dato rischio sul capitale. Se il valore di stop loss non può garantire l'obiettivo di rischio per il lotto di negoziazione più basso possibile su quel simbolo, viene automaticamente spostato al livello SL massimo possibile. Questo ci aiuta a valutare quanto spazio abbiamo per lo stop loss per un dato rischio.

Dopo aver calcolato il rischio e il rendimento, gli oggetti rettangolo vengono aggiornati sul grafico.

void EA_dragBuyHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

// BUY
   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);

   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()<SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), 
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

L’EA_dragSellHandle() viene attivato per la configurazione dell'ordine di vendita.

I calcoli si basano sul prezzo di symbolInfo.Bid() e i rettangoli vengono disegnati di conseguenza, ovvero con la parte in verde che indica che il profitto è inferiore al livello di prezzo corrente. 

void EA_dragSellHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double maxSLAllowed=MM.GetMaxSLPossible(symbolInfo.Bid(),ORDER_TYPE_SELL);

   if(SL_price>maxSLAllowed)
     {
      SL_price=maxSLAllowed;
      SL_arrow.SetDouble(OBJPROP_PRICE,0,maxSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()>SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Bid(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(),
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

L’EA_placeOrder() viene attivato dopo che l'oggetto m_placeOrderButton è stato premuto. Posizionerà l'ordine di mercato di acquisto o vendita per i livelli SL e TP calcolati e la dimensione del lotto data.

Puoi notare quanto sia facile effettuare ordini di mercato utilizzando la classe CExpertTrade. 

bool EA_placeOrder()
  {
   symbolInfo.RefreshRates();
   visualRRDialog.ResetButtons();

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
      orderPlaced=trade.Buy(visualRRDialog.GetOrderLots(),symbolInfo.Ask(),
                            visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
      orderPlaced=trade.Sell(visualRRDialog.GetOrderLots(),symbolInfo.Bid(),
                            visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));

   return orderPlaced;
  }

Il gestore EA_editParamsUpdate() viene attivato premendo il tasto Invio dopo aver modificato uno dei campi di modifica: riskRatioEdit, riskValueEdit e orderLotsEdit.

Quando ciò accade, è necessario ricalcolare la dimensione del lotto consentita, il livello TP, la massima perdita di tick, il rischio azionario e il rendimento:

void EA_editParamsUpdate()
  {
   MM.Percent(visualRRDialog.GetRiskPercent());

   SL_arrow.GetDouble(OBJPROP_PRICE, 0, SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME, 0, startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);
   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), 
                                     SL_price, visualRRDialog.GetOrderLots()));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price, 
                                       visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();
   rectUpdate(visualRRDialog.GetOrderType());

   ChartRedraw();
  }

Si ricorre a EA_onTick() ogni volta che arriva un nuovo tick. I calcoli vengono eseguiti solo se l'ordine non è stato ancora inserito e il livello di stop loss è già stato scelto trascinando il puntatore SL_arrow.

Una volta inserito l'ordine, il rischio, il rendimento, il livello TP, così come il ridisegno del rischio e del rendimento non sono più necessari. 

void EA_onTick()
  {
   if(SL_price!=0.0 && orderPlaced==false)
     {
      double lotSize=0.0;
      SL_price=visualRRDialog.GetSL();
      symbolInfo.RefreshRates();

      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
         lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
         lotSize=MM.CheckOpenShort(symbolInfo.Ask(),SL_price);

      visualRRDialog.SetMaxAllowedLots(lotSize);
      if(visualRRDialog.GetOrderLots()>lotSize) visualRRDialog.SetOrderLots(lotSize);

      visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());

      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
         visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), 
                                           SL_price, visualRRDialog.GetOrderLots()));
         visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price, 
                                             visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
        }
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
         visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(
                                           symbolInfo.Bid(), SL_price, visualRRDialog.GetOrderLots()));
         visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(), SL_price, 
                                             visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
        }
      visualRRDialog.Refresh();
      rectUpdate(visualRRDialog.GetOrderType());
     }

   ChartRedraw(0);
  }

La funzione rectUpdate() ha il compito di ridisegnare i rettangoli a colori di rischio e rendimento. I punti di controllo comprendono l'ora di avvio dell'oggetto SL_arrow, il valore del prezzo Ask o Bid corrente a seconda del tipo di ordine e i livelli SL e TP. Il rettangolo rosa chiaro mostra la fascia di prezzo tra il prezzo corrente e il livello SL, il rettangolo verde chiaro mostra la fascia di prezzo tra il prezzo corrente e il livello TP.

Entrambi i rettangoli sono un ottimo strumento per osservare l'impatto del rapporto rischio/rendimento sui livelli di prezzo SL e TP e aiutano a regolare il rischio prima di iniziare il trading.

void rectUpdate(ENUM_ORDER_TYPE orderType)
  {
   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   if(orderType==ORDER_TYPE_BUY)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Ask(),currentTime,symbolInfo.Ask()+
                       (symbolInfo.Ask()-visualRRDialog.GetSL())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Ask());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
   else if(orderType==ORDER_TYPE_SELL)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Bid(),currentTime,symbolInfo.Bid()-
                        (visualRRDialog.GetSL()-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Bid());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
  }

 

7. Demo

Guarda la seguente demo che mostra l'Expert Advisor in azione. Sto eseguendo un ordine di vendita dopo un grande rimbalzo avvenuto poco dopo l'apertura del mercato di lunedì 01/11/2010.

Per una migliore esperienza di visualizzazione, imposta il video a schermo intero e la qualità a 480p. I commenti sono inclusi nel video:

 

Conclusione

Nel seguente articolo ho presentato un modo per creare un Expert Advisor interattivo per il trading manuale, basato su rischio predefinito e rapporto rischio/rendimento.

Ho mostrato come utilizzare le classi standard per visualizzare il contenuto sul grafico e come gestire gli eventi del grafico per l'immissione di nuovi dati e la gestione degli oggetti drag-and-drop. Spero che le idee che ho esposto serviranno come base per costruire altri strumenti visivi configurabili in MQL5.

Tutti i file sorgente e le bitmap sono allegati all'articolo.

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

File allegati |
visualrrea.mq5 (13.84 KB)
crrdialog.mqh (13.95 KB)
visualrrids.mqh (0.8 KB)
images.zip (159.05 KB)
Progettare e implementare nuovi widget GUI basati sulla classe CChartObject Progettare e implementare nuovi widget GUI basati sulla classe CChartObject
Dopo l’articolo sull’Expert Advisor semiautomatico con interfaccia GUI che ho scritto in precedenza, ho realizzato che sarebbe stato opportuno migliorare l'interfaccia con alcune nuove funzionalità per gli indicatori e gli Expert Advisor più complessi. Dopo aver familiarizzato con le classi della libreria standard MQL5 ho implementato nuovi widget. Questo articolo descrive un processo di progettazione e implementazione di nuovi widget GUI MQL5 che possono essere utilizzati negli indicatori e negli Expert Advisor. I widget presentati nell'articolo sono CChartObjectSpinner, CChartObjectProgressBar e CChartObjectEditTable.
Un Avvio Rapido o una Breve Guida per Principianti Un Avvio Rapido o una Breve Guida per Principianti
Ciao caro lettore! In questo articolo, cercherò di spiegarti e mostrarti come puoi imparare facilmente e rapidamente i principi della creazione dell’Expert Advisor, lavorare con gli indicatori, ecc. È rivolto ai principianti e non presenterà alcun esempio difficile o astruso.
Calcoli paralleli su MetaTrader 5 Calcoli paralleli su MetaTrader 5
Il tempo ha sempre avuto un grande valore in tutta la storia dell'umanità e noi ci sforziamo di non sprecarlo inutilmente. Questo articolo ti dirà come accelerare il lavoro del tuo Expert Advisor se il tuo computer ha un processore multi-core. Inoltre, l'implementazione del metodo proposto non richiede la conoscenza di altri linguaggi oltre a MQL5.
Visualizza una Strategia nel Tester MetaTrader 5 Visualizza una Strategia nel Tester MetaTrader 5
Conosciamo tutti il detto "Meglio vedere una volta che sentire cento volte". Puoi leggere vari libri su Parigi o Venezia, ma in base alle immagini mentali non avresti le stesse sensazioni di una passeggiata serale in queste favolose città. Il vantaggio della visualizzazione può essere facilmente proiettato su qualsiasi aspetto della nostra vita, incluso il lavoro sul mercato, ad esempio l'analisi dei prezzi sui grafici utilizzando indicatori e, naturalmente, la visualizzazione dello strategy testing. Questo articolo contiene le descrizioni di tutte le funzionalità di visualizzazione del Tester MetaTrader 5 Strategy.