OnTester

La fonction est appelée dans les Expert Advisors lorsque l'évènement Tester se produit pour effectuer les actions nécessaires après le test.

double  OnTester(void);

Valeur de Retour

Valeur du critère d'optimisation pour s'assurer des résultats du test.  

Note

La fonction OnTester() ne peut être utilisée que lors du test d'EA et est prévue pour le calcul de la valeur utilisée comme critère 'Max personnalisé' lors de l'optimisation des paramètres d'entrée.

Pendant l'optimisation génétique, le tri des résultats dans une seule génération est effectué en tri descendant. Cela signifie que les résultats ayant la plus grande valeur sont réputés être les meilleurs du point de vue du critère d'optimisation. Les pires valeurs du tri sont placées à la fin et sont donc ignorées. Elles ne prennent donc pas part dans la formation de la génération suivante.

La fonction OnTester() vous permet donc non seulement de créer et de sauver vos rapports de résultats de test, mais aussi de contrôler le processus d'optimisation pour trouver les meilleurs paramètres de la stratégie de trading.

Ci-dessous se trouve un exemple de calcul du critère personnalisé d'optimisation. L'idée est de calculer la régression linéaire du graphique du solde. Elle est décrite dans l'article Optimiser une stratégie en utilisant le graphique du solde et en comparant les résultats du critère "Solde + max Sharpe Ratio".

//+------------------------------------------------------------------+
//|                                              OnTester_Sample.mq5 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property description "EA exemple avec la fonction OnTester()"
#property description "Comme critère d'optimisation personnalisé, "
#property description "le rapport de la régression linéaire du graphique du solde"
#property description "divisé par l'erreur quadratique moyenne est retourné"
//--- inclut la classe pour les opérations de trading
#include <Trade\Trade.mqh>
//--- paramètres d'entrée de l'EA
input double Lots               = 0.1;     // Volume
input int    Slippage           = 10;      // Slippage autorisé
input int    MovingPeriod       = 80;      // Période de la moyenne mobile
input int    MovingShift        = 6;       // Décalage de la moyenne mobile
//--- variables globales
int    IndicatorHandle=0;  // handle de l'indicateur
bool   IsHedging=false;    // flag pour le compte
CTrade trade;              // pour effectuer les opérations de trading
//--- 
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| Vérifie les conditions d'ouverture de position                   |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- ne trade qu'au début d'une nouvelle barre
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("Echec de CopyRates avec ",_Symbol,", aucun historique");
      return;
     }
//--- volume du tick
   if(rt[1].tick_volume>1)
      return;
//--- valeurs de la moyenne mobile
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("Echec de CopyBuffer de iMA, aucune données");
      return;
     }
//--- vérifie la présence d'un signal
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- la bougie a ouvert plus haut, mais a fermé en dessous de la moyenne mobile
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_BUY;    // signal d'achat
   else // bougie ouverte en dessous mais fermée au-dessus de la moyenne mobile
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_SELL;// signal de vente
     }
//--- vérifications supplémentaires
   if(signal!=WRONG_VALUE)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
        {
         double price=SymbolInfoDouble(_Symbol,signal==ORDER_TYPE_SELL ? SYMBOL_BID:SYMBOL_ASK);
         trade.PositionOpen(_Symbol,signal,Lots,price,0,0);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Vérifie les conditions de fermeture de position                  |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- ne trade qu'au début d'une nouvelle barre
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("Echec de CopyRates avec ",_Symbol,", aucun historique");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- valeurs de la moyenne mobile
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("Echec de CopyBuffer de iMA, aucune données");
      return;
     }
//--- la position a déjà été sélectionnée auparavant avec PositionSelect()
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);
//--- la bougie a ouvert plus haut, mais a fermé en dessous de la moyenne mobile - fermeture d'une position courte
   if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
//--- la bougie a ouvert plus bas, mais a fermé au-dessus de la moyenne mobile - fermeture d'une position longue
   if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- vérifications supplémentaires
   if(signal)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         trade.PositionClose(_Symbol,Slippage);
     }
//---
  }
//+-------------------------------------------------------------------+
//| Sélectionne une position suivant le compte: Netting ou Hedging    |
//+-------------------------------------------------------------------+
bool SelectPosition()
  {
   bool res=false;
//--- sélectionne une position pour un compte de type Hedging
   if(IsHedging)
     {
      uint total=PositionsTotal();
      for(uint i=0; i<total; i++)
        {
         string position_symbol=PositionGetSymbol(i);
         if(_Symbol==position_symbol && EA_MAGIC==PositionGetInteger(POSITION_MAGIC))
           {
            res=true;
            break;
           }
        }
     }
//--- sélectionne une position pour un compte de type Netting
   else
     {
      if(!PositionSelect(_Symbol))
         return(false);
      else
         return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //---vérifie le numéro magique
     }
//--- résultat de l'exécution
   return(res);
  }
//+------------------------------------------------------------------+
//| Fonction d'initialisation de l'expert                            |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- définit le type de trading : Netting ou Hedging
   IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- initialise un objet pour le contrôle correct de la position
   trade.SetExpertMagicNumber(EA_MAGIC);
   trade.SetMarginMode();
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetDeviationInPoints(Slippage);
//--- crée l'indicateur de la moyenne mobile
   IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(IndicatorHandle==INVALID_HANDLE)
     {
      printf("Erreur lors de la création de l'indicateur iMA");
      return(INIT_FAILED);
     }
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Fonction de tick de l'Expert                                     |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- si une position est déjà ouverte, vérifie la condition de fermeture
   if(SelectPosition())
      CheckForClose();
// vérifie la position d'ouverture de position
   CheckForOpen();
//---
  }
//+------------------------------------------------------------------+
//| Fonction de test                                                 |
//+------------------------------------------------------------------+
double OnTester()
  {
//--- valeur d'optimisation du critère personnalisé
   double ret=0.0;
//--- récupère les résultats du trade dans le tableau
   double array[];
   double trades_volume;
   GetTradeResultsToArray(array,trades_volume);
   int trades=ArraySize(array);
//--- s'il y a moins de 10 trades, le test ne donne aucun résultat positif
   if(trades<10)
      return (0);
//--- résultat moyen par trade
   double average_pl=0;
   for(int i=0;i<ArraySize(array);i++)
      average_pl+=array[i];
   average_pl/=trades;
//--- affiche le message pour le mode de test simple
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
      PrintFormat("%s: Trades=%d, Profit moyen=%.2f",__FUNCTION__,trades,average_pl);
//--- calcul des ratios de régression linéaire pour le graphique du profit
   double a,b,std_error;
   double chart[];
   if(!CalculateLinearRegression(array,chart,a,b))
      return (0);
//--- calcule l'erreur de la déviation du graphique depuis la ligne de régression
   if(!CalculateStdError(chart,a,b,std_error))
      return (0);
//--- calcule le ratio des bénéfices tendanciels sur l'écart type
   ret=(std_error == 0.0) ? a*trades : a*trades/std_error;
//--- retourne la valeur du critère personnalisé d'optimisation
   return(ret);
  }
//+------------------------------------------------------------------+
//| Récupère le tableau des profits/pertes des transactions          |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
  {
//--- demande l'historique de trading complet
   if(!HistorySelect(0,TimeCurrent()))
      return (false);
   uint total_deals=HistoryDealsTotal();
   volume=0;
//--- définit la taille initiale du tableau avec une marge - par le nombre de transactions dans l'historique
   ArrayResize(pl_results,total_deals);
//--- compteur de transactions qui fixe le résultat de trading - perte ou profit
   int counter=0;
   ulong ticket_history_deal=0;
//--- parcours toutes les transactions
   for(uint i=0;i<total_deals;i++)
     {
      //--- sélectionne une transaction
      if((ticket_history_deal=HistoryDealGetTicket(i))>0)
        {
         ENUM_DEAL_ENTRY deal_entry  =(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket_history_deal,DEAL_ENTRY);
         long            deal_type   =HistoryDealGetInteger(ticket_history_deal,DEAL_TYPE);
         double          deal_profit =HistoryDealGetDouble(ticket_history_deal,DEAL_PROFIT);
         double          deal_volume =HistoryDealGetDouble(ticket_history_deal,DEAL_VOLUME);
         //--- nous ne sommes intéressés que dans les opérations de trading 
         if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
            continue;
         //--- seules les transactions fixent les pertes/profits
         if(deal_entry!=DEAL_ENTRY_IN)
           {
            //--- écrit le résultat de trading dans le tableau et augmente le compteur de transactions
            pl_results[counter]=deal_profit;
            volume+=deal_volume;
            counter++;
           }
        }
     }
//--- définit la taille finale du tableau
   ArrayResize(pl_results,counter);
   return (true);
  }
//+------------------------------------------------------------------+
//| Calcule la régression linéaire y=a*x+b                           |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double  &change[],double &chartline[],
                               double  &a_coef,double  &b_coef)
  {
//--- vérifie la suffisance des données
   if(ArraySize(change)<3)
      return (false);
//--- crée un tableau du graphique avec une accumulation
   int N=ArraySize(change);
   ArrayResize(chartline,N);
   chartline[0]=change[0];
   for(int i=1;i<N;i++)
      chartline[i]=chartline[i-1]+change[i];
//--- calcule maintenant les ratios de régression
   double x=0,y=0,x2=0,xy=0;
   for(int i=0;i<N;i++)
     {
      x=x+i;
      y=y+chartline[i];
      xy=xy+i*chartline[i];
      x2=x2+i*i;
     }
   a_coef=(N*xy-x*y)/(N*x2-x*x);
   b_coef=(y-a_coef*x)/N;
//---
   return (true);
  }
//+------------------------------------------------------------------+
//| Calculer l'erreur d'écart quadratique moyen pour a et b spécifiés|
//+------------------------------------------------------------------+
bool  CalculateStdError(double  &data[],double  a_coef,double  b_coef,double &std_err)
  {
//--- somme des carrés d'erreur
   double error=0;
   int N=ArraySize(data);
   if(N<=2)
      return (false);
   for(int i=0;i<N;i++)
      error+=MathPow(a_coef*i+b_coef-data[i],2);
   std_err=MathSqrt(error/(N-2));
//--- 
   return (true);
  }

Voir également

Test de stratégies de trading, TesterHideIndicators, Travailler avec les résultats d'optimisation, TesterStatistics, OnTesterInit, OnTesterDeinit, OnTesterPass, MQL_TESTER, MQL_OPTIMIZATION, FileOpen, FileWrite, FileLoad, FileSave