OnTester

Die Funktion wird von EAs aufgerufen, wenn das Ereignis Tester auftritt, um notwendige Aktionen nach dem Test durchzuführen.

double  OnTester(void);

Rückgabewert

Der Wert des benutzerdefinierten Kriteriums, um auf das Testergebnis zuzugreifen.  

Hinweis

Die Funktion OnTester() kann nur beim Testen von EAs verwendet werden und ist in erster Linie für die Berechnung eines Wertes gedacht, der als Kriterium 'Custom max' bei der Optimierung von Eingabeparametern verwendet wird.

Bei der genetischen Optimierung erfolgt die Sortierung innerhalb einer Generation in absteigender Reihenfolge. Dies bedeutet, dass die Ergebnisse mit dem höchsten Wert aus Sicht des Optimierungskriteriums als die besten angesehen werden. Die schlechtesten Werte für eine solche Sortierung werden am Ende platziert und anschließend verworfen. Deshalb beteiligen sie sich nicht an der Bildung der nächsten Generation.

Mit der Funktion OnTester() können Sie also nicht nur Ihre eigenen Testergebnisse erstellen und speichern, sondern auch den Optimierungsprozess steuern, um die besten Parameter der Handelsstrategie zu finden.

Unten ist ein Beispiel für die Berechnung einer benutzerdefinierten Kriterienoptimierung. Die Idee ist, die lineare Regression der Saldenkurve zu berechnen. Das wird im Artikel Optimieren einer Strategie unter Verwendung einer Kurve der Salden und dem Vergleich der Ergebnisse mit dem Kriterium "Balance + max Sharpe Ratio" beschrieben.

//+------------------------------------------------------------------+
//|                                              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 "Sample EA with the OnTester() handler"
#property description "As a custom optimization criterion, "
#property description "the ratio of the balance graph linear regression"
#property description "divided by the deviation mean-square error is returned"
//--- Einbinden der Klasse mit den Handelsoperationen
#include <Trade\Trade.mqh>
//--- Eingabeparameter des EAs
input double Lots               = 0.1;     // Volumen
input int    Slippage           = 10;      // erlaubter Schlupf
input int    MovingPeriod       = 80;      // Periodenlänge des gleitenden Durchschnitts
input int    MovingShift        = 6;       // Versazt des gleitenden Durchschnitts
//--- Globale Variablen
int    IndicatorHandle=0;  // Handle des Indikators
bool   IsHedging=false;    // Flag des Kontos
CTrade trade;              // für die Durchführung der Handelsoperationen
//--- 
#define EA_MAGIC 18052018
//+------------------------------------------------------------------+
//| Prüfen der Bedingung für eine Positionseröffnung                 |
//+------------------------------------------------------------------+
void CheckForOpen(void)
  {
   MqlRates rt[2];
//--- Handeln nur zu Beginn einer neuen Bar
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
//--- Tick-Volumen
   if(rt[1].tick_volume>1)
      return;
//--- Erhalt der Werte des gleitenden Durchschnitts
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- Prüfen auf ein Signal
   ENUM_ORDER_TYPE signal=WRONG_VALUE;
//--- Kerze eröffnete über, schloss aber unter dem gleitenden Durchschnitt
   if(rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=ORDER_TYPE_BUY;    // Kaufsignal
   else // Kerze eröffnete unter, schloss aber über dem gleitenden Durchschnitt
     {
      if(rt[0].open<ma[0] && rt[0].close>ma[0])
         signal=ORDER_TYPE_SELL;// Verkaufssignal
     }
//--- zusätzliche Prüfungen
   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);
        }
     }
//---
  }
//+------------------------------------------------------------------+
//| Prüfen der Bedingung für eine Positionsschließung                |
//+------------------------------------------------------------------+
void CheckForClose(void)
  {
   MqlRates rt[2];
//--- Handeln nur zu Beginn einer neuen Bar
   if(CopyRates(_Symbol,_Period,0,2,rt)!=2)
     {
      Print("CopyRates of ",_Symbol," failed, no history");
      return;
     }
   if(rt[1].tick_volume>1)
      return;
//--- Erhalt der Werte des gleitenden Durchschnitts
   double   ma[1];
   if(CopyBuffer(IndicatorHandle,0,1,1,ma)!=1)
     {
      Print("CopyBuffer from iMA failed, no data");
      return;
     }
//--- Position wurde bereits früher mittels PositionSelect() ausgewählt
   bool signal=false;
   long type=PositionGetInteger(POSITION_TYPE);
//--- Kerze eröffnete über, schloss aber unter dem gleitenden Durchschnitt - Schließen der Verkaufsposition
   if(type==(long)POSITION_TYPE_SELL && rt[0].open>ma[0] && rt[0].close<ma[0])
      signal=true;
//--- Kerze eröffnete unter, schloss aber über dem gleitenden Durchschnitt - Schließen der Kaufposition
   if(type==(long)POSITION_TYPE_BUY && rt[0].open<ma[0] && rt[0].close>ma[0])
      signal=true;
//--- zusätzliche Prüfungen
   if(signal)
     {
      if(TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>100)
         trade.PositionClose(_Symbol,Slippage);
     }
//---
  }
//+-------------------------------------------------------------------+
//| Positionsauswahl je nach Kontotyp: Netting oder Hedging           |
//+-------------------------------------------------------------------+
bool SelectPosition()
  {
   bool res=false;
//--- Auswahl einer Position für ein Hedging-Konto
   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;
           }
        }
     }
//--- Auswahl einer Position für ein Netting-Konto
   else
     {
      if(!PositionSelect(_Symbol))
         return(false);
      else
         return(PositionGetInteger(POSITION_MAGIC)==EA_MAGIC); //--- Prüfen der Magicnummer
     }
//--- Berechnungsergebnis
   return(res);
  }
//+------------------------------------------------------------------+
//| Expert Initialisierungsfunktion                                  |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- Setzen des Kontotyps: Netting oder Hedging
   IsHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
//--- Initialisieren eines Objekts auf die korrekte Position
   trade.SetExpertMagicNumber(EA_MAGIC);
   trade.SetMarginMode();
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetDeviationInPoints(Slippage);
//--- Erstellen des Gleitenden Durchschnitts
   IndicatorHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
   if(IndicatorHandle==INVALID_HANDLE)
     {
      printf("Error creating iMA indicator");
      return(INIT_FAILED);
     }
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Tick-Funktion des Experten                                       |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//--- wenn eine Position bereits eröffnet wurde, prüfen der Bedingung sie zu schließen
   if(SelectPosition())
      CheckForClose();
//--- prüfen der Bedingung die offene Position zu schließen
   CheckForOpen();
//---
  }
//+------------------------------------------------------------------+
//| Tester Funktion                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//--- Nutzerkriterium der Optimierung (je höher desto besser)
   double ret=0.0;
//--- Übertrage der Handelsergebnisse in das Array
   double array[];
   double trades_volume;
   GetTradeResultsToArray(array,trades_volume);
   int trades=ArraySize(array);
//--- Gibt es weniger als 10 Positionen, ist das Testergebnis 0
   if(trades<10)
      return (0);
//--- Durchschnittsergebnis der Positionen
   double average_pl=0;
   for(int i=0;i<ArraySize(array);i++)
      average_pl+=array[i];
   average_pl/=trades;
//--- Anzeige der Nachricht im Einzeltestmodus
   if(MQLInfoInteger(MQL_TESTER) && !MQLInfoInteger(MQL_OPTIMIZATION))
      PrintFormat("%s: Trades=%d, Average profit=%.2f",__FUNCTION__,trades,average_pl);
//--- Berechnen der Linearen Regression für den Saldengraph
   double a,b,std_error;
   double chart[];
   if(!CalculateLinearRegression(array,chart,a,b))
      return (0);
//--- Berechnen des Fehlers der Abweichung des Charts von der Regressionslinie
   if(!CalculateStdError(chart,a,b,std_error))
      return (0);
//--- Berechnen des Verhältnisses des Trendgewinns und der Standardabweichung
   ret=(std_error == 0.0) ? a*trades : a*trades/std_error;
//--- Rückgabe des Wertes des Nutzerkriteriums der Optimierung
   return(ret);
  }
//+------------------------------------------------------------------+
//| Abfrage des Arrays von Gewinn/Verlust der Deals                  |
//+------------------------------------------------------------------+
bool GetTradeResultsToArray(double &pl_results[],double &volume)
  {
//--- Abfrage der kompletten Handelshistorie
   if(!HistorySelect(0,TimeCurrent()))
      return (false);
   uint total_deals=HistoryDealsTotal();
   volume=0;
//--- Setzen die Anfangsgröße des Arrays mit einer Marge - nach der Anzahl der Deals in der Historie.
   ArrayResize(pl_results,total_deals);
//--- Zähler der Deals, die das Handelsergebnis bestimmen - Gewinn oder Verlust
   int counter=0;
   ulong ticket_history_deal=0;
//--- über alle Deals
   for(uint i=0;i<total_deals;i++)
     {
      //--- Auswahl eines Deals
      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);
         //--- uns interessieren nur die Handelsoperationen
         if((deal_type!=DEAL_TYPE_BUY) && (deal_type!=DEAL_TYPE_SELL))
            continue;
         //--- Nur die Deals, die Gewinn/Verlust festlegen
         if(deal_entry!=DEAL_ENTRY_IN)
           {
            //--- Schreiben des Handelsergebnisses in den Array und Erhöhen des Zähler der Deals
            pl_results[counter]=deal_profit;
            volume+=deal_volume;
            counter++;
           }
        }
     }
//--- Setzen der finalen Größe des Arrays
   ArrayResize(pl_results,counter);
   return (true);
  }
//+------------------------------------------------------------------+
//| Berechnen der Linearen Regression y=a*x+b                        |
//+------------------------------------------------------------------+
bool CalculateLinearRegression(double  &change[],double &chartline[],
                               double  &a_coef,double  &b_coef)
  {
//--- Prüfen auf genügend Daten
   if(ArraySize(change)<3)
      return (false);
//--- Erstellen eines Charts mit Akkumulation
   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];
//--- Jetzt folgt die Berechnung des Regressionsverhältnisses
   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);
  }
//+------------------------------------------------------------------+
//|  Berechnen des Fehlerquadrate zum Ermitteln von a und b          |
//+------------------------------------------------------------------+
bool  CalculateStdError(double  &data[],double  a_coef,double  b_coef,double &std_err)
  {
//--- Summe der Fehlerquadrate
   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);
  }

Siehe auch

Testen von Handelsstrategien, TesterHideIndicators, Arbeit mit Ergebnisse der Optimierung, TesterStatistics, OnTesterInit, OnTesterDeinit, OnTesterPass, MQL_TESTER, MQL_OPTIMIZATION, FileOpen, FileWrite, FileLoad, FileSave