English Русский 中文 Español 日本語 Português
preview
Visuelle Auswertung der Optimierungsergebnisse

Visuelle Auswertung der Optimierungsergebnisse

MetaTrader 5Tester | 3 März 2022, 11:00
393 0
Aleksandr Slavskii
Aleksandr Slavskii

Einführung

Ein nutzerdefiniertes Optimierungskriterium bietet eine sehr bequeme Einrichtung für die Optimierung von Expert Advisors. Wenn wir jedoch mehrere Kriterien prüfen müssen, sollten wir mehrere Optimierungen durchführen, was zeitaufwändig sein kann. Eine bessere Lösung wäre die Möglichkeit, mehrere nutzerdefinierte Kriterien während einer Optimierung zu testen. Außerdem wäre es schön, wenn man sofort die Diagramme von Saldo (eng: balance) und Kapitalwert (eng: equity) sehen könnte.

Es ist immer gut, wenn man verschiedene Visualisierungsmöglichkeiten hat. Unser Gehirn nimmt mehr als achtzig Prozent der Informationen über die Augen auf. In diesem Artikel befassen wir uns also mit der Erstellung von Optimierungsdiagrammen und der Auswahl des optimalen nutzerdefinierten Kriteriums.

Wir werden auch sehen, wie man eine gewünschte Lösung mit wenig MQL5-Kenntnissen erstellen kann, indem man die auf der Website veröffentlichten Artikel und Forumskommentare verwendet.


Formulierung der Aufgabe

  1. Sammeln Sie die Daten der einzelnen Optimierungsdurchgänge.
  2. Erstellen Sie Salden-/Kapitaldiagramme für jeden Optimierungsdurchgang.
  3. Berechnen Sie mehrere nutzerdefinierte Optimierungskriterien.
  4. Sortieren Sie die Diagramme nach dem nutzerdefinierten Optimierungskriterium in aufsteigender Reihenfolge.
  5. Zeigen Sie die besten Ergebnisse für alle nutzerdefinierten Kriterien.


Schritte zur Problemlösung

Da wir den Code des Expert Advisors ohnehin ändern müssen, wollen wir versuchen, diese Änderungen zu minimieren.

  • Daher wird der gesamte Datenerfassungscode in einer separaten Include-Datei SkrShotOpt.mqh implementiert, während das nutzerdefinierte Kriterium in der Datei CustomCriterion.mqh berechnet wird.
  • Der Screenshot ScreenShotOptimization.mq5 dient der Erstellung von Diagrammen und der Speicherung von Screenshots.

Daher müssen wir nur wenige Codezeilen in den Expert Advisor einfügen.


1. Das Sammeln von Daten. SkrShotOpt.mqh

Die maximalen und minimalen Kapitalwerte werden in die Funktion OnTick() geschrieben.

   double _Equity = AccountInfoDouble(ACCOUNT_EQUITY);
   if(tempEquityMax < _Equity)
      tempEquityMax = _Equity;
   if(tempEquityMin > _Equity)
      tempEquityMin = _Equity;

Um zu vermeiden, dass Positionsänderungen bei jedem Tick überprüft werden müssen, werden Positionsänderungen in der Funktion OnTradeTransaction() verfolgt:

void IsOnTradeTransaction(const MqlTradeTransaction & trans,
                          const MqlTradeRequest & request,
                          const MqlTradeResult & result)
  {
   if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
      if(HistoryDealSelect(trans.deal))
        {
         if(_deal_entry != DEAL_ENTRY_OUT && _deal_entry != DEAL_ENTRY_OUT_BY)
            _deal_entry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
         if(trans.deal_type == DEAL_TYPE_BUY || trans.deal_type == DEAL_TYPE_SELL)
            if(_deal_entry == DEAL_ENTRY_IN || _deal_entry == DEAL_ENTRY_OUT || _deal_entry == DEAL_ENTRY_INOUT || _deal_entry == DEAL_ENTRY_OUT_BY)
               allowed = true;
        }
  }

Wenn sich die Anzahl der offenen Positionen ändert, werden die Felder für den Saldo und das Eigenkapital gefüllt.

   if(allowed) // if there was a trade
     {
      double accBalance = AccountInfoDouble(ACCOUNT_BALANCE);
      double accEquity = AccountInfoDouble(ACCOUNT_EQUITY);

      ArrayResize(balance, _size + 1);
      ArrayResize(equity, _size + 1);
      balance[_size] = accBalance;

      if(_deal_entry != DEAL_ENTRY_OUT && _deal_entry != DEAL_ENTRY_OUT_BY) // if a new position appeared
         equity[_size] = accEquity;
      else // if position closed
        {
         if(changesB < accBalance)
            equity[_size] = tempEquityMin;
         else
            switch(s_view)
              {
               case  min_max_E:
                  equity[_size] = tempEquityMax;
                  break;
               default:
                  equity[_size] = tempEquityMin;
                  break;
              }
         tempEquityMax = accEquity;
         tempEquityMin = accEquity;
        }

      _size = _size + 1;
      changesPos = PositionsTotal();
      changesB = accBalance;
      _deal_entry = -1;
      allowed = false;
     }

Die Größe der Datei mit Frames ist begrenzt. Bei einer großen Anzahl von Handelspositionen wird die Datei immer größer und ist schwer zu verarbeiten. Daher sollten nur die notwendigsten Informationen in die Datei geschrieben werden.

Wenn eine Position eröffnet wird, speichern wir den Saldo und den Kapitalwert:

  • Am Ende schreiben wir den maximalen Kapitalwert, wenn die Position mit einem Verlust geschlossen wurde.
  • den kleinsten Kapitalwert, wenn die Position mit einem Gewinn abgeschlossen wurde. 

Somit hat fast jede Position vier Werte, die in Arrays geschrieben werden: Saldo und Kapitalwert bei Eröffnung, Saldo und Max/Min -Kapitalwert bei Schließung.

Es kann vorkommen, dass eine Position geschlossen und eine andere im selben Tick eröffnet wird. In diesem Fall wird nur eine Position geschrieben. Dies hat keinen Einfluss auf die Visualisierung der Diagramme, reduziert aber die Anzahl der Arrays erheblich.

 

Speichern der gesammelten Daten in einer Datei.

Es ist nur sinnvoll, gewinnbringende Optimierungsdurchläufe zu sammeln. Dieser Parameter ist in den Einstellungen implementiert, sodass Sie bei Bedarf zusätzlich verlustbringende Durchgänge registrieren können. Was die Vorwärtsdurchläufe betrifft, so werden sie alle aufgezeichnet.

Mit der Funktion FrameAdd() werden die gesammelten Daten am Ende jedes einzelnen Durchlaufs in eine Datei geschrieben, wenn das Ereignis Tester eintritt. Das Tester-Ereignis wird wiederum von der Funktion OnTester() verarbeitet.

bool  FrameAdd( 
   const string  name,        // public name/tag
   long          id,          // public id 
   double        value,       // value
   const void&   data[]       // array of any type
   );

Ein ausführliches und anschauliches Beispiel für die Arbeit mit der Funktion FrameAdd() finden Sie hier: https://www.mql5.com/ru/forum/11277/page4#comment_469771

Da FrameAdd() nur ein Array und einen numerischen Wert 'value' schreiben kann, es aber gut ist, zusätzlich zu Saldo und Kapitalwert alle Werte der Aufzählung ENUM_STATISTICS zu übergeben, werden die Daten sequentiell in ein Array geschrieben, während die Arraygröße in den übergebenen numerischen Wert 'value' geschrieben wird.

   if(id == 1)  // if it is a backward pass
     {
      // if profit % and the number of trades exceed those specified in the settings, the pass is written into the file
      if(TesterStatistics(STAT_PROFIT) / TesterStatistics(STAT_INITIAL_DEPOSIT) * 100 > _profit && TesterStatistics(STAT_TRADES) >= trades)
        {
         double TeSt[42]; // total number of elements in the ENUM_STATISTICS enumeration is 41
         IsRecordStat(TeSt); // writing testing statistics to the array
         IsCorrect(); // adjusting balance and equity arrays

         if(m_sort != none)
           {
            while((sort)size_sort != none)
               size_sort++;
            double LRB[], LRE[], coeff[];
            Coeff = Criterion(balance, equity, LRB, LRE, TeSt, coeff, 3);// calculating custom criterion
            ArrayInsert(balance, equity, _size + 1, 0);     // joining balance and equity arrays into one
            ArrayInsert(balance, TeSt, (_size + 1) * 2, 0); // add to the resulting array the array with the ENUM_STATISTICS data
            FrameAdd(name, id, _size + 1, balance);         // write the frame into the file
           }
         else
           {
            ArrayInsert(balance, equity, _size + 1, 0);     // joining balance and equity arrays into one
            ArrayInsert(balance, TeSt, (_size + 1) * 2, 0); // add to the resulting array the array with the ENUM_STATISTICS data
            FrameAdd(name, id, _size + 1, balance);         // write the frame into the file
           }
        }
     }

Vorwärtsduchläufe werden ähnlich wie Rückwärtsdurchläufe behandelt, sind aber eigentlich die Folge der Optimierung. Aus diesem Grund werden für sie nur Salden und Kapitalwerte geschrieben, ohne ENUM_STATISTICS-Werte.

Wenn zum Endzeitpunkt des Tests eine offene Position besteht, wird sie vom Tester geschlossen.

Das bedeutet, dass wir eine Position praktisch schließen (den aktuellen Saldo und Kapitalwert schreiben), wenn die Variable, die die Anzahl der offenen Geschäfte speichert, zum Zeitpunkt des Testendes nicht gleich ist.

void IsCorrect()
  {
   if(changesPos > 0) // if there is an open position by the testing end time, it should be virtually closed as the tester will close such a position
     {
      _size++;
      ArrayResize(balance, _size + 1);
      ArrayResize(equity, _size + 1);
      if(balance[_size - 2] > AccountInfoDouble(ACCOUNT_BALANCE))
        {
         balance[_size - 1] = AccountInfoDouble(ACCOUNT_BALANCE);
         switch(s_view)
           {
            case  min_max_E:
               equity[_size - 1] = tempEquityMax;
               break;
            default:
               equity[_size - 1] = tempEquityMin;
               break;
           }
        }
      else
        {
         balance[_size - 1] = AccountInfoDouble(ACCOUNT_BALANCE);
         equity[_size - 1] = tempEquityMin;
        }
      balance[_size] = AccountInfoDouble(ACCOUNT_BALANCE);
      equity[_size] = AccountInfoDouble(ACCOUNT_EQUITY);
     }
   else
     {
      ArrayResize(balance, _size + 1);
      ArrayResize(equity, _size + 1);
      balance[_size] = AccountInfoDouble(ACCOUNT_BALANCE);
      equity[_size] = AccountInfoDouble(ACCOUNT_EQUITY);
     }
  }

Damit ist das Schreiben der Daten ist abgeschlossen.


Lesen der Daten aus der Datei. ScreenShotOptimization.mq5

Nach der Optimierung wird eine Datei mit Frames unter dem folgenden Pfad erstellt: C:\Users\*User-Name*\AppData\Roaming\MetaQuotes\Terminal\Terminalkennung\MQL5\Files\Tester. Die Datei trägt den Namen EA_name.symbol.timeframe.mqd. Auf die Datei kann nicht sofort nach der Optimierung zugegriffen werden. Wenn Sie jedoch das Terminal neu starten, kann auf die Datei mit regulären Dateifunktionen zugegriffen werden.

Suchen Sie die Datei unter C:\Users\*User-Name*\AppData\Roaming\MetaQuotes\Terminal\terminal ID\MQL5\Files\Tester.

   int count = 0;
   long search_handle = FileFindFirst("Tester\\*.mqd", FileName);
   do
     {
      if(FileName != "")
         count++;
      FileName = "Tester\\" + FileName;
     }
   while(FileFindNext(search_handle, FileName));
   FileFindClose(search_handle);

Zunächst wird das Lesen in die Struktur eingeordnet.

FRAME Frame = {0};
FileReadStruct(handle, Frame);
struct FRAME
  {
   ulong             Pass;
   long              ID;
   short             String[64];
   double            Value;
   int               SizeOfArray;
   long              Tmp[2];

   void              GetArrayB(int handle, Data & m_FB)
     {
      ArrayFree(m_FB.Balance);
      FileReadArray(handle, m_FB.Balance, 0, (int)Value);
      ArrayFree(m_FB.Equity);
      FileReadArray(handle, m_FB.Equity, 0, (int)Value);
      ArrayFree(m_FB.TeSt);
      FileReadArray(handle, m_FB.TeSt, 0, (SizeOfArray / sizeof(m_FB.TeSt[0]) - (int)Value * 2));
     }
   void              GetArrayF(int handle, Data & m_FB, int size)
     {
      FileReadArray(handle, m_FB.Balance, size, (int)Value);
      FileReadArray(handle, m_FB.Equity, size, (int)Value);
     }
  };

In den FRAME-Strukturfunktionen werden Datenstrukturfunktionen gefüllt, aus denen weitere Diagramme aufgebaut werden.

struct Data
  {
   ulong             Pass;
   long              id;
   int               size;
   double            Balance[];
   double            Equity[];
   double            LRegressB[];
   double            LRegressE[];
   double            coeff[];
   double            TeSt[];
  };
Data                 m_Data[];

Da das Zeichnen von Tausenden von Screenshots sehr zeitintensiv ist, geben Sie in den Skripteinstellungen einen Parameter an, der das Speichern von Screenshots deaktiviert, wenn der Gewinn unter dem angegebenen Prozentsatz liegt.

Die Datei mit den Bildern wird in einer Schleife verarbeitet. 

Um ein Diagramm zu zeichnen, benötigen wir ein Datenfeld. Daher werden zuerst alle Durchläufe, die das Kriterium des Mindestgewinns erfüllen, geschrieben.

Dann werden alle Rückwärtsdurchläufe iteriert, und die entsprechenden Vorwärtsdurchläufe werden entsprechend der Durchlaufnummer für sie ausgewählt. Das Array mit den Durchläufen in Vorwärtsrichtung wird dem Array mit den Durchläufen in Rückwärtsrichtung hinzugefügt.

Die Lösung kann zwei Arten von Diagrammen zeichnen. Einer davon ähnelt dem Graphen im Strategietester, d.h. der Durchlauf beginnt mit der Starteinlage.

Die zweite Variante des Vorwärtsdurchlaufs beginnt mit dem Depot, mit dem der Rückwärtsdurchlauf endete. In diesem Fall wird der Gewinnwert des Rückwärtsdurchlaufs zum Saldo und zum Kapitalwert des Vorwärtsdurchlaufs addiert und an das Ende des Arrays des Rückwärtsdurchlaufs geschrieben.

Dies geschieht natürlich nur, wenn die Optimierung mit einer Vorwärtsperiode durchgeführt wird.

   int handle = FileOpen(FileName, FILE_READ | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_BIN);
   if(handle != INVALID_HANDLE)
     {
      FileSeek(handle, 260, SEEK_SET);

      while(Res && !IsStopped())
        {
         FRAME Frame = {0};
         // read from the file to the Frame structure
         Res = (FileReadStruct(handle, Frame) == sizeof(Frame));
         if(Res)
            if(Frame.ID == 1) // if it is a Backward pass, write data to the m_Data structure
              {
               ArrayResize(m_Data, size + 1);
               m_Data[size].Pass = Frame.Pass;
               m_Data[size].id = Frame.ID;
               m_Data[size].size = (int)Frame.Value;
               Frame.GetArrayB(handle, m_Data[size]);  // write data to the m_Data structure arrays
               // if profit of this pass corresponds to the input settings, immediately calculate optimization criteria
               if(m_Data[size].TeSt[STAT_PROFIT] / m_Data[size].TeSt[STAT_INITIAL_DEPOSIT] * 100 >= profitPersent)
                 {
                  Criterion(m_Data[size].Balance, m_Data[size].Equity, m_Data[size].LRegressB, m_Data[size].LRegressE, m_Data[size].TeSt, m_Data[size].coeff, m_lineR);
                  size++;
                 }
              }
            else  // if it is a Forward pass, write to the end of the m_Data data structures
               if(m_Forward != BackOnly) // if drawing of only Backward passes is not selected in settings
                  for(int i = 0; i < size; i++)
                    {
                     if(Frame.Pass == m_Data[i].Pass) // if Back and Forward pass numbers match
                       {
                        int m = 0;
                        if(m_Forward == Back_Next_Forward) // if selected drawing of Forward graph as a continuation of Backward
                          {
                           Frame.GetArrayF(handle, m_Data[i], m_Data[i].size - 1); // write data at the end of the the m_Data structure array, with a one-trade shift
                           for(int x = m_Data[i].size - 1; x < m_Data[i].size + (int)Frame.Value - 1; x++)
                             {
                              m_Data[i].Balance[x] = m_Data[i].Balance[x] + m_Data[i].TeSt[STAT_PROFIT]; //  add profit of the Backward test to the Forward pass
                              m_Data[i].Equity[x] = m_Data[i].Equity[x] + m_Data[i].TeSt[STAT_PROFIT];
                             }
                           m = 1;
                          }
                        else
                           Frame.GetArrayF(handle, m_Data[i], m_Data[i].size); // if drawing of a Forward pass from a starting balance is selected

                        m_Data[i].coeff[Forward_Trade] = (int)(Frame.Value / 2); // number of forward trades (not exact))
                        m_Data[i].coeff[Profit_Forward] = m_Data[i].Balance[m_Data[i].size + (int)Frame.Value - m - 1] - m_Data[i].Balance[m_Data[i].size - m];
                        break;
                       }
                     if(i == size - 1) // if no Backward is found for this Forward pass, move the file pointer to the end of writing
                        FileSeek(handle, Frame.SizeOfArray, SEEK_CUR); // of this frame as if we read array data from the file
                    }
        }
      FileClose(handle);
      //---


Konstruktion der Diagramme

Das Plotten der Graphen.

string _GraphPlot(double& y1[],
                  double& y2[],
                  double& LRegressB[],
                  double& LRegressE[],
                  double& coeff[],
                  double& TeSt[],
                  ulong pass)
  {
   CGraphic graphic;
//--- create graphic
   bool res = false;
   if(ObjectFind(0, "Graphic") >= 0)
      res = graphic.Attach(0, "Graphic");
   else
      res = graphic.Create(0, "Graphic", 0, 0, 0, _width, _height);

   if(!res)
      return(NULL);

   graphic.BackgroundMain(FolderName);  // print the Expert Advisor name
   graphic.BackgroundMainSize(FontSet + 1); // font size for the Expert Advisor name

   graphic.IndentLeft(FontSet);
   graphic.HistoryNameSize(FontSet); // font size for the line names
   graphic.HistorySymbolSize(FontSet);

   graphic.XAxis().Name("pass " + IntegerToString(pass)); // show the pass number along the X axis
   graphic.XAxis().NameSize(FontSet + 1);

   graphic.XAxis().ValuesSize(12); // price font size
   graphic.YAxis().ValuesSize(12);

//--- add curves
   CCurve *curve = graphic.CurveAdd(y1, ColorToARGB(clrBlue), CURVE_POINTS_AND_LINES, "Balance"); // plot the balance graph
   curve.LinesWidth(widthL);  // graph line width
   curve.PointsSize(widthL + 1); // size of dots on the balance graph

   CCurve *curve1 = graphic.CurveAdd(y2, ColorToARGB(clrGreen), CURVE_LINES, "Equity");  // plot the equity graph
   curve1.LinesWidth(widthL);

   int size = 0;
   switch(m_lineR) // plot the regression line
     {
      case  lineR_Balance: // balance regression line
        {
         size = ArraySize(LRegressB);
         CCurve *curve2 = graphic.CurveAdd(LRegressB, ColorToARGB(clrBlue), CURVE_LINES, "LineR_Balance");
         curve2.LinesWidth(widthL);
        }
      break;
      case  lineR_Equity: // equity regression line
        {
         size = ArraySize(LRegressE);
         CCurve *curve2 = graphic.CurveAdd(LRegressE, ColorToARGB(clrRed), CURVE_LINES, "LineR_Equity");
         curve2.LinesWidth(widthL);
        }
      break;
      case  lineR_BalanceEquity: // balance and equity regression line
        {
         size = ArraySize(LRegressB);
         CCurve *curve2 = graphic.CurveAdd(LRegressB, ColorToARGB(clrBlue), CURVE_LINES, "LineR_Balance");
         curve2.LinesWidth(widthL);

         CCurve *curve3 = graphic.CurveAdd(LRegressE, ColorToARGB(clrRed), CURVE_LINES, "LineR_Equity");
         curve2.LinesWidth(widthL);
        }
      break;
      default:
         break;
     }
//--- plot curves
   graphic.CurvePlotAll();

// Important!!!  All lines and captions must be created after creating the graph; otherwise, the graph will override them

   if(size == 0)
     {
      size = ArraySize(LRegressE);
      if(size == 0)
         size = ArraySize(LRegressB);
     }

   int x1 = graphic.ScaleX(size - 1); //Scales the value of the number of trades along the X axis
   graphic.LineAdd(x1, 30, x1, _height - 45, ColorToARGB(clrBlue), LINE_END_BUTT); // construct the vertical line denoting the end of the Backward period

   string txt = "";
   int txt_x = 70;// text indent along the X axis
   int txt_y = 30;// text indent along the Y axis

   graphic.FontSet("Arial", FontSet);// Set current font parameters

   for(int i = 0; i < size_sort; i++)  // Write all coefficients and criteria on the chart
     {
      if(coeff[i] == 0)
         continue;
      if(i == 1 || i == 3)
         txt = StringFormat("%s = %d", EnumToString((sort)i), (int)coeff[i]);
      else
         if(i == 0 || i == 2)
            txt = StringFormat("%s = %.2f", EnumToString((sort)i), coeff[i]);
         else
            txt = StringFormat("%s = %.4f", EnumToString((sort)i), coeff[i]);
      graphic.TextAdd(txt_x, txt_y + FontSet * i, txt, ColorToARGB(clrGreen));
     }

   txt_y = txt_y + FontSet * (size_sort - 1);
   txt = StringFormat("Profitability = %.2f", TeSt[STAT_PROFIT_FACTOR]);
   graphic.TextAdd(txt_x, txt_y + FontSet, txt, ColorToARGB(clrGreen));
   txt = StringFormat("Expected  payoff = %.2f", TeSt[STAT_EXPECTED_PAYOFF]);
   graphic.TextAdd(txt_x, txt_y + FontSet * 2, txt, ColorToARGB(clrGreen));

   graphic.Update();
//--- return resource name
   return graphic.ChartObjectName();
  }


Weitere Informationen über die Arbeit mit CGraphic finden Sie in den folgenden Artikeln:


Die Screenshots der Diagramme werden in einem separaten Ordner unter dem Verzeichnis der Dateien Files gespeichert. Der Ordnername lautet EA_name.Symbol.Zeitrahmen.

bool BitmapObjectToFile(const string ObjName, const string _FileName, const bool FullImage = true)
  {
   if(ObjName == "")
      return(true);

   const ENUM_OBJECT Type = (ENUM_OBJECT)ObjectGetInteger(0, ObjName, OBJPROP_TYPE);
   bool Res = (Type == OBJ_BITMAP_LABEL) || (Type == OBJ_BITMAP);

   if(Res)
     {
      const string Name = __FUNCTION__ + (string)MathRand();

      ObjectCreate(0, Name, OBJ_CHART, 0, 0, 0);
      ObjectSetInteger(0, Name, OBJPROP_XDISTANCE, -5e3);

      const long chart = ObjectGetInteger(0, Name, OBJPROP_CHART_ID);

      Res = ChartSetInteger(chart, CHART_SHOW, false) && ObjectCreate(chart, Name, OBJ_BITMAP_LABEL, 0, 0, 0) &&
            ObjectSetString(chart, Name, OBJPROP_BMPFILE, ObjectGetString(0, ObjName, OBJPROP_BMPFILE)) &&
            (FullImage || (ObjectSetInteger(chart, Name, OBJPROP_XSIZE, ObjectGetInteger(0, ObjName, OBJPROP_XSIZE)) &&
                           ObjectSetInteger(chart, Name, OBJPROP_YSIZE, ObjectGetInteger(0, ObjName, OBJPROP_YSIZE)) &&
                           ObjectSetInteger(chart, Name, OBJPROP_XOFFSET, ObjectGetInteger(0, ObjName, OBJPROP_XOFFSET)) &&
                           ObjectSetInteger(chart, Name, OBJPROP_YOFFSET, ObjectGetInteger(0, ObjName, OBJPROP_YOFFSET)))) &&
            ChartScreenShot(chart, FolderName + "\\" + _FileName, (int)ObjectGetInteger(chart, Name, OBJPROP_XSIZE), (int)ObjectGetInteger(chart, Name, OBJPROP_YSIZE));
      ObjectDelete(0, Name);
     }

   return(Res);
  }


Dies sind die resultierenden Diagramme.

Die Diagramme im Ordner.

Wenn die Speicherung aller Screenshots gewählt worden ist, bestehen die Screenshotnamen aus dem nutzerdefinierten Kriterium, das für die Sortierung ausgewählt wurde + Gewinn + Durchlaufnummer. 

Wenn nur die besten Durchläufe ausgewählt werden, setzen sich die Screenshotnamen aus dem nutzerdefinierten Kriterium + Gewinn zusammen.

So sieht das vom Skript erstellte Diagramm aus.


Unten sehen Sie das gleiche Diagramm aus dem Strategietester


Ich habe hier sehr ähnliche Diagramme gezeigt, aber in den meisten Fällen werden sie unterschiedlich sein. Das liegt daran, dass im Strategietester die Positionen entlang der X-Achse an die Zeit gebunden sind, während das Skript Diagramme erstellt, in denen die X-Achse an die Anzahl der Positionen gebunden ist. Außerdem sind die Kapitalwerte für die Analyse der vom Skript erstellten Diagramme nicht ausreichend, da wir nur ein Minimum an Informationen in einen Frame schreiben müssen, um die Datei klein genug zu halten. Gleichzeitig reichen diese Daten aus, um eine erste Bewertung der Effizienz eines Optimierungsdurchlaufs vorzunehmen. Sie sind auch ausreichend für die Berechnung eines nutzerdefinierten Optimierungskriteriums.

Starten Sie nach der Optimierung, bevor Sie die ScreenShotOptimization ausführen, das Terminal neu!

Ursprünglich wollte ich nur alle Optimierungsgraphen visualisieren. Aber als ich das Skript implementierte und siebentausend Screenshots im Ordner sah, wurde mir klar, dass es unmöglich ist, mit so vielen Graphen zu arbeiten. Stattdessen müssen wir die besten von ihnen anhand bestimmter Kriterien auswählen.

Ich habe schon vor langer Zeit festgestellt, dass algorithmische Händler in zwei Kategorien fallen:

  1. Die einen glauben, dass ein EA in einem sehr großen Zeitraum optimiert werden sollte, der mehreren Jahren oder sogar Dutzenden von Jahren entspricht und nach dem der EA funktionieren wird.
  2. Andere sind der Meinung, dass ein EA regelmäßig reoptimiert werden muss, und zwar in einem kleinen Zeitraum, z. B. ein Optimierungsmonat + eine Handelswoche, oder drei Optimierungsmonate + ein Handelsmonat oder ein anderer geeigneter Reoptimierungszeitplan.

Ich gehöre zum zweiten Typ.

Deshalb habe ich mich auf die Suche nach Optimierungskriterien gemacht, die als Filter für die Auswahl der besten Durchläufe dienen sollen.


Erstellen von nutzerdefinierten Optimierungskriterien

Alle nutzerdefinierten Optimierungskriterien werden in einer separaten Include-Datei CustomCriterion.mqh berechnet, da diese Berechnungen sowohl in der Skript-Operation zum Zeichnen der Diagramme als auch im Expert Advisor, den wir optimieren, verwendet werden.

Bevor ich mein eigenes nutzerdefiniertes Optimierungskriterium erstellt habe, habe ich eine Menge verwandtes Material gefunden.

R² als Gütemaß der Saldenkurve einer Strategie

Der Artikel enthält eine detaillierte Beschreibung einer linearen Regression und ihrer Berechnung mit Hilfe der AlgLib-Bibliothek. Er enthält auch eine gute Beschreibung des Bestimmtheitsmaßes R^2 und seiner Anwendung bei der Prüfung von Ergebnissen. Ich empfehle die Lektüre dieses Artikels.

Die Funktion zur Berechnung der linearen Regression, R^2, und ProfitStability:

void Coeff(double& Array[], double& LR[], double& coeff[], double& TeSt[], int total, int c)
  {
//-- Fill the matrix: Y - Array value, X - ordinal number of the value
   CMatrixDouble xy(total, 2);
   for(int i = 0; i < total; i++)
     {
      xy[i].Set(0, i);
      xy[i].Set(1, Array[i]);
     }

//-- Find coefficients a and b of the linear model y = a*x + b;
   int retcode = 0;
   double a, b;
   CLinReg::LRLine(xy, total, retcode, b, a);

//-- Generate the linear regression values for each X;
   ArrayResize(LR, total);
   for(int x = 0; x < total; x++)
      LR[x] = x * a + b;

   if(m_calc == c)
     {
      //-- Find the coefficient of correlation of values with their linear regression
      corr = CAlglib::PearsonCorr2(Array, LR);

      //-- Find R^2 and its sign
      coeff[r2] = MathPow(corr, 2.0);
      int sign = 1;
      if(Array[0] > Array[total - 1])
         sign = -1;
      coeff[r2] *= sign;

      //-- Find LR Standard Error
      if(total - 2 == 0)
         stand_err = 0;
      else
        {
         for(int i = 0; i < total; i++)
           {
            double delta = MathAbs(Array[i] - LR[i]);
            stand_err =  stand_err + delta * delta;
           }
         stand_err = MathSqrt(stand_err / (total - 2));
        }
     }
//-- Find ProfitStability = Profit_LR/stand_err
   if(stand_err == 0)
      coeff[ProfitStability] = 0;
   else
      coeff[ProfitStability] = (LR[total - 1] - LR[0]) / stand_err;
  }


Optimieren Sie die Strategie nach dem Saldo und vergleichen Sie die Ergebnisse mit dem Kriterium "Saldo + maximale Sharpe Ratio".

Aus diesem Artikel habe ich die Berechnung des individuellen Optimierungskriteriums ProfitStability übernommen. Seine Berechnung ist einfach: Zunächst berechnen wir den LR-Standardfehler, d.h. die durchschnittliche Abweichung der Regressionslinie von der Gleichgewichts- oder Kapitalwertlinie. Dann wird vom resultierenden Wert der Regressionslinie der Ausgangswert abgezogen, um TrendProfit zu erhalten.

Die ProfitStability wird als Verhältnis von TrendProfit und LR Standardabweichung berechnet:

Der Artikel beschreibt detailliert alle Vor- und Nachteile dieses Optimierungskriteriums. Er enthält auch eine Reihe von Tests zum Vergleich von ProfitStability mit anderen Optimierungskriterien.

Da die lineare Regression sowohl für den Saldo als auch für den Kapitalwert berechnet werden kann und die ProfitStability an die lineare Regressionsrechnung gebunden ist, wird die Berechnung der ProfitStability in die lineare Regressionsrechnung implementiert.


Erstellen von nutzerdefinierten Kriterien zur Optimierung von Expert Advisors

Es ist ein ziemlich alter Artikel aus dem Jahr 2011, aber er ist interessant und immer noch relevant. Ich habe eine Formel zur Berechnung des Trading System Safety Factor (TSSF) aus diesem Artikel verwendet.

TSSF = Avg.Win / Avg.Loss ((110% - %Win) / (%Win-10%) + 1)

   if(TeSt[STAT_PROFIT_TRADES] == 0 || TeSt[STAT_LOSS_TRADES] == 0 || TeSt[STAT_TRADES] == 0)
      coeff[TSSF] = 0;
   else
     {
      double  avg_win = TeSt[STAT_GROSS_PROFIT] / TeSt[STAT_PROFIT_TRADES];
      double  avg_loss = -TeSt[STAT_GROSS_LOSS] / TeSt[STAT_LOSS_TRADES];
      double  win_perc = 100.0 * TeSt[STAT_PROFIT_TRADES] / TeSt[STAT_TRADES];
      //  Calculate the secure ratio for this percentage of profitable deals:
      if((win_perc - 10.0) + 1.0 == 0)
         coeff[TSSF] = 0;
      else
        {
         double  teor = (110.0 - win_perc) / (win_perc - 10.0) + 1.0;
         //  Calculate the real ratio:
         double  real = avg_win / avg_loss;
         if(teor != 0)
            coeff[TSSF] = real / teor;
         else
            coeff[TSSF] = 0;
        }
     }


Optimale Vorgehensweise für Entwicklung und Analyse von Handelssystemen

In diesem Artikel habe ich den LinearFactor verwendet, der wie folgt berechnet wird:

  • LinearFactor = MaxDeviation/EndBalance
  • MaxDeviaton = Max(MathAbs(Balance[i]-AverageLine))
  • AverageLine=StartBalance+K*i
  • K=(EndBalance-StartBalance)/n
  • n - Anzahl der Positionen im Test

Einzelheiten finden Sie in dem oben genannten Artikel. Der Artikel ist sehr interessant und bietet viele nützliche Informationen.

Mit Blick auf die Zukunft möchte ich sagen, dass es mir nicht gelungen ist, ein universelles nutzerdefiniertes Optimierungskriterium zu finden, das zu jedem Expert Advisor passt. Verschiedene Kriterien liefern die besten Ergebnisse für verschiedene Expert Advisor.

In einigen EAs hat LinearFactor wunderbare Ergebnisse.

   double MaxDeviaton = 0;
   double K = (Balance[total - 1] - Balance[0]) / total;
   for(int i = 0; i < total; i++)
     {
      if(i == 0)
         MaxDeviaton = MathAbs(Balance[i] - (Balance[0] + K * i));
      else
         if(MathAbs(Balance[i] - (Balance[0] + K * i) > MaxDeviaton))
            MaxDeviaton = MathAbs(Balance[i] - (Balance[0] + K * i));
     }
   if(MaxDeviaton ==0 || Balance[0] == 0)
      coeff[LinearFactor] = 0;
   else
      coeff[LinearFactor] = 1 / (MaxDeviaton / Balance[0]);

In der persönlichen Korrespondenz erwähnte der Autor, dass dieses Kriterium weiter verstärkt werden kann, und er erklärte, wie, aber ich konnte diese Tipps nicht codieren.


Also fügte ich vier nutzerdefinierte Optimierungskriterien in den Code ein.

  1. R^2 - Bestimmtheitskoeffizient.
  2. ProfitStability (GewinnStabilität).
  3. TSSF - Handelssystem-Sicherheitsfaktor
  4. LinearFactor.

Alle diese Optimierungskriterien sind in unserem Projekt enthalten.

Leider konnte ich "Komplexes Kriterium, Maximum" nicht hinzufügen, weil ich nicht herausfinden konnte, wie es berechnet wird. 


Mein Optimierungskriterium

Auf der Grundlage all dieser Artikel können wir mit der Erstellung unseres eigenen Optimierungskriteriums fortfahren.

Welche Saldenkurve wollen wir sehen? Ideal wäre natürlich eine stetig wachsende gerade Linie.

Schauen wir uns ein Diagramm mit Gewinn an.



Bei der Optimierung vergleichen wir nicht die Ergebnisse mehrerer EAs, sondern es handelt sich um die Ergebnisse ein und desselben EAs, weshalb ich beschlossen habe, die Zeit, in der der EA Gewinn erzielt hat, nicht zu berücksichtigen.

Auch die Volumina berücksichtige ich nicht. Aber wenn das Lot dynamisch berechnet wird, ist es notwendig, die Volumina irgendwie in die Berechnung des nutzerdefinierten Kriteriums einzubeziehen (dies ist nicht implementiert).

Wie viele Positionen? Es ist mir egal, wie viele Positionen mir Tausende einbringen, eine oder hundert Positionen, also werde ich auch die Anzahl der Positionen nicht berücksichtigen. Bitte beachten Sie jedoch, dass bei zu wenigen Positionen die lineare Regression falsch berechnet wird.

Was ist in diesem Diagramm wichtig? Zunächst einmal ist es der Gewinn. Ich habe mich entschieden, den relativen Gewinn auszuwerten, d.h. den Gewinn im Verhältnis zum Ausgangsbestand.

Relative_Prof = TeSt[STAT_PROFIT] / TeSt[STAT_INITIAL_DEPOSIT];

Ein weiterer sehr wichtiger Parameter ist der Drawdown

Wie wird der Drawdown im Tester berechnet? Der maximale Kapitalwert auf der linken Seite wird mit dem minimalen Kapitalwert auf der rechten Seite verglichen.


Die Werte oberhalb des Saldos sind eher unangenehm - das Geld, das wir nicht verdienen konnten. Aber wenn die Werte unter dem Saldo liegen, tut es richtig weh.

Für mich ist der maximale Drawdown unterhalb des Saldos am wichtigsten. Der Handel sollte also nicht allzu sehr schmerzen.


double equityDD(const double & Balance[], const double & Equity[], const double & TeSt[], const double & coeff[], const int total)
  {
   if(TeSt[STAT_INITIAL_DEPOSIT] == 0)
      return(0);

   double Balance_max = Balance[0];
   double Equity_min = Equity[0];
   difference_B_E = 0;
   double Max_Balance = 0;

   switch((int)TeSt[41])
     {
      case  0:
         difference_B_E = TeSt[STAT_EQUITY_DD];
         break;
      default:
         for(int i = 0; i < total - 1; i++)
           {
            if(Balance_max < Balance[i])
               Balance_max = Balance[i];
            if(Balance[i] == 10963)
               Sleep(1);
            if(Balance_max - Equity[i + 1] > difference_B_E)
              {
               Equity_min = Equity[i + 1];
               difference_B_E = Balance_max - Equity_min;
               Max_Balance = Balance_max;
              }
           }
         break;
     }

   return(1 - difference_B_E / TeSt[STAT_INITIAL_DEPOSIT]);
  }

Da die Werte der nutzerdefinierten Kriterien in aufsteigender Reihenfolge berücksichtigt werden sollten, habe ich den Drawdown von einem abgezogen. Je höher also der Wert, desto geringer der Drawdown.

Den daraus resultierenden Wert nannte ich equity_rel, d.h. Drawdown relativ zum Startguthaben.

Es stellte sich heraus, dass für die korrekte Berechnung von equity_rel die bisher verwendete Methode der Kapitalwerterfassung nicht geeignet ist. Da einige der minimalen Kapitalwerte verloren gehen, musste ich zwei Varianten implementieren, um die Kapitalwerte zu speichern. Die erste Variante speichert die maximalen Kapitalwerte, wenn mit einem Verlust abgeschlossen wird, und die minimalen Werte, wenn mit einem Gewinn abgeschlossen wird. Bei der zweiten Variante werden nur die minimalen Kapitalwerte gespeichert.

Um das Skript über die verwendete Methode zur Erfassung der Kapitalwerte zu informieren, wurden diese Optionen in das Array mit der Testerstatistik TeSt[41] geschrieben. Außerdem werden in der Funktion EquityDD() der Kapitalwert und die Differenz_B_E entsprechend der Methode der Kapitalerfassung berechnet.

//---

  Als Nächstes habe ich beschlossen, verschiedene Daten zu kombinieren und das Ergebnis zu überprüfen.

//---

Auf der Grundlage von equity_rel ist es möglich, einen alternativen Erholungsfaktor zu berechnen. 

difference_B_E — maximaler Kapitaldrawdown in Geldwerten.

coeff[c_recovery_factor] = coeff[Profit_Bak] / difference_B_E;

Um das Diagramm einer geraden Linie anzunähern, habe ich R^2 zum zweiten alternativen Erholungsfaktor hinzugefügt

coeff[c_recovery_factor_r2] = coeff[Profit_Bak] / difference_B_E * coeff[r2];

Da in den Einstellungen die Berechnung der Korrelation auf der Grundlage des Saldos oder des Kapitalwerts ausgewählt werden kann, wird R^2 mit dem Drawdown korrelieren, wenn wir nur die Mindestwerte des Kapitalwerts erfassen.

Die Formel "relativer Gewinn * R^2" kann interessante Ergebnisse für das nutzerdefinierte Kriterium liefern.

coeff[profit_r2] = relative_prof * coeff[r2];

Es wäre auch sinnvoll, zu berücksichtigen, wie groß die Korrelation war. Daher lautet das nächste nutzerdefinierte Kriterium wie folgt.

Relativer Gewinn * R^2 / Standardfehler

   if(stand_err == 0)
      coeff[profit_r2_Err] = 0;
   else
      coeff[profit_r2_Err] = relative_prof * coeff[r2] / stand_err;

Da wir nun den relativen Gewinn, den Rückgang des Kapitalwerts im Verhältnis zum Startguthaben und R^2 kennen, können wir eine Formel erstellen, die den Gewinn, den Rückgang des Kapitalwerts und die Nähe der Kurve zu einer Geraden berücksichtigt:

relative_prof + equity_rel + r2;

Was aber, wenn wir einem dieser Parameter mehr Bedeutung beimessen wollen? Also habe ich die Gewichtungsvariable 'ratio' hinzugefügt. 

Jetzt haben wir drei weitere nutzerdefinierte Optimierungskriterien.

coeff[profit_R_equity_r2] = relative_prof * ratio + coeff[equity_rel] + coeff[r2];

coeff[profit_equity_R_r2] = relative_prof + coeff[equity_rel] * ratio + coeff[r2];

coeff[profit_equity_r2_R] = relative_prof + coeff[equity_rel] + coeff[r2] * ratio;


Insgesamt haben wir zwölf nutzerdefinierte Optimierungskriterien.

1. R^2 - Bestimmungskoeffizient

2.  ProfitStability

3. TSSF - der Sicherheitsfaktor des Handelssystems

4.  LinearFactor

5.  equity_rel  

6. c_recovery_factor

7. c_recovery_factor_r2

8. profit_r2

9. profit_r2_Err

10. profit_R_equity_r2

11. profit_equity_R_r2

12. profit_equity_r2_R


Prüfen des Ergebnisses

Um das Ergebnis zu überprüfen, müssen wir einen einfachen Expert Advisor erstellen...

Laut dem vorläufigen Artikelplan sollte hier ein einfacher Expert Advisor Code stehen. Leider haben zwei einfache EAs nicht die gewünschten Ergebnisse gezeigt.

Daher musste ich einen der erstellten EAs nehmen, um die Ergebnisse mit ihm zu zeigen (ich habe den Namen des EAs versteckt).

Nehmen wir an, es ist Ende April und wir planen, den EA auf einem echten Konto einzusetzen. Wie findet man heraus, welches Kriterium man optimieren muss, damit er mit Gewinn handelt?

Starten wir eine dreimonatige Vorwärtsoptimierung.  



Starten Sie das Terminal nach der Optimierung neu.

Führen Sie das Skript aus und wählen Sie in den Einstellungen nur die besten Ergebnisse aus. Hier sind die Ergebnisse im Ordner.


Dann wähle ich visuell den besten Durchlauf aus allen Durchläufen aus. Da ich mehrere ähnliche Ergebnisse hatte, wählte ich profit_equity_R_r2, da bei dieser Optimierung der geringere Drawdown Priorität hat.


Der gleiche Zeitraum sieht im Strategietester wie folgt aus:


Hier ist der maximale Saldo zum Vergleich:


Hier ist das Komplexe Kriterium max:


Wie Sie sehen können, gibt es mit dem besten profit_equity_R_r2 Durchlauf viel weniger Handelspositionen auf dem Chart, als mit der maximalen Balance und dem maximalen Complex, der Gewinn ist ungefähr der gleiche, aber der Chart ist viel glatter.


Wir haben also das nutzerdefinierte Kriterium bestimmt: profit_equity_R_r2. Schauen wir uns nun an, was passieren würde, wenn wir die Optimierung für die letzten drei Monate durchführen und, nachdem wir während der Optimierung die besten Einstellungen erhalten haben, beschließen würden, dieses Setup im Mai zu handeln.

Führen wir eine Vorwärtsoptimierung durch und prüfen wir.

Einstellungen für die Optimierung.  


Legen Sie in den EA-Einstellungen das nutzerdefinierte Kriterium fest, für das die Optimierung durchgeführt werden soll.

Wenn wir also den EA für die letzten drei Monate mit dem nutzerdefinierten Kriterium profit_equity_R_r2 optimiert haben,

und dann mit den erhaltenen Einstellungen vom 1. April bis zum 1. Mai gehandelt hätten, würden wir 750 Einheiten mit einem Kapitalwert von 300 Einheiten verdienen.



Prüfen wir nun die Leistung des Validate EA von fxsaber!

Prüfen wir, wie der EA vier Monate lang handeln würde. Einstellungen von Validate: drei Optimierungsmonate und ein Handelsmonat.


Wie Sie sehen können, hat der EA diesen Stresstest überstanden!

Vergleichen wir ihn mit dem Chart, der die gleichen Einstellungen hat, aber für Complex Criterion max optimiert ist.



Der EA hat überlebt, aber...


Schlussfolgerung

Vorteile:

  1. Sie können alle Graphen der Optimierungsergebnisse gleichzeitig sehen.
  2. Die Möglichkeit, ein optimales nutzerdefiniertes Optimierungskriterium für Ihren EA zu finden.

Nachteile:

Aufgrund einer Begrenzung der aufgezeichneten Daten sind die Diagramme weniger aussagekräftig als die im Strategietester.

Bei einer großen Anzahl von Positionen wächst die Datei mit den Frames enorm an und wird unlesbar.

//---

Wie die Experimente gezeigt haben, gibt es nicht das einzige Superkriterium: Verschiedene Kriterien liefern die besten Ergebnisse für verschiedene Expert Advisor.

Aber wir haben eine ganze Reihe solcher Kriterien. Wenn die Leser die Idee unterstützen, wird die Auswahl noch größer sein.

//---

Einer der Tester hat vorgeschlagen, die Skripteinstellung im Artikel zu beschreiben, damit die Nutzer, die nicht bereit sind, das Material zu studieren, einfach den Code verwenden können, ohne die Details zu studieren.


Die Verwendung

Um diesen Code zu verwenden, laden Sie die unten angehängte Zip-Datei herunter, entpacken Sie sie und kopieren Sie sie in den MQL5-Ordner.

Wählen Sie im Terminal Datei -> Datenordner öffnen -> rechter Mausklick an einer leeren Stelle im neuen Ordner -> "Einfügen". Wenn Sie gefragt werden, ob Sie Dateien im Zielordner ersetzen wollen, wählen Sie "Ersetzen".

Starten Sie dann MetaEditor, öffnen Sie Ihren EA und nehmen Sie die folgenden Änderungen vor:

1.  Fügen Sie IsOnTick() in die OnTick()-Funktion ein;

 2. Fügen Sie den folgenden Code am Ende Ihres EAs ein:

  #include <SkrShotOpt.mqh>     

  double OnTester() {return(IsOnTester());}

  void OnTradeTransaction(const MqlTradeTransaction & trans, const MqlTradeRequest & request,const MqlTradeResult & result) 
    {
      IsOnTradeTransaction(trans, request, result);
     }
 Wenn der EA bereits über die Funktion OnTradeTransaction() verfügt, fügen Sie ihr IsOnTradeTransaction(trans, request, result) hinzu;

3. Drücken Sie "Kompilieren". 

Wenn Fehler generiert werden, die auf übereinstimmende Variablennamen hinweisen, müssen Sie die Namen ändern.


Einstellungen

Sobald Sie den Code eingefügt haben, erscheinen einige zusätzliche Zeilen in den EA-Einstellungen.

Aktivieren Sie keinesfalls die Kästchen zur Optimierung dieser Einstellungen!!! Diese Einstellungen haben keinen Einfluss auf die Optimierungsergebnisse, also optimieren Sie sie nicht.


  • Write the pass if trades more than — Wenn Ihr EA viele Positionen handelt, kann dieser Parameter erhöht werden, um die Menge der in die Frames-Dateien geschriebenen Daten zu reduzieren.
  • Write the pass if profit exceeds % — Verlustbringende Durchläufe werden standardmäßig entfernt. Sie können diesen Parameter ändern, wenn Sie nicht möchten, dass der Gewinn unter einem bestimmten Prozentsatz des Startguthabens liegt.
  • Select equity values — wählen Sie "save only min equity" ein, wenn Sie die korrekte Berechnung der folgenden nutzerdefinierten Kriterien benötigen: quity_rel, c_recovery_factor, c_recovery_factor_r2, profit_r2, profit_r2_Err, profit_R_equity_r2, profit_equity_R_r2, profit_equity_r2_R
    Wenn Sie Diagramme ähnlich denen im Strategietester haben möchten, wählen Sie "save min and max equity"

  • Custom max — wenn Sie "none" wählen, werden Frames in der Datei aufgezeichnet, aber es wird kein nutzerdefiniertes Kriterium berechnet (um die Optimierungszeit nicht zu erhöhen).

In diesem Fall können Sie jedoch keine Optimierung nach nutzerdefinierten Kriterien auswählen. Außerdem haben alle unten aufgeführten Parameter keine Wirkung.

Sie sollten ein nutzerdefiniertes Kriterium in diesem Parameter auswählen, wenn Sie die Optimierung nach einem nutzerdefinierten Kriterium durchführen möchten. 

Beachten Sie, dass die Berechnung eines nutzerdefinierten Kriteriums von den Parametern "Kapitalwerte auswählen" und "Kriterium berechnen nach" abhängt.

  • Calculate criterion by - R^2 auf der Grundlage von Bilanz oder Kapitalwert berechnen; wirkt sich auf alle nutzerdefinierten Kriterien aus, bei denen R^2 verwendet wird.
r2, profit_r2, profit_r2_Err, profit_R_equity_r2, profit_equity_R_r2, profit_equity_r2_R

    //----

    Einstellungen des Skripts


    • Draw regression line: Wählen Sie, welche Regressionslinie gezeichnet werden soll - Saldo, Kapitalwert, Saldo und Kapitalwert, keine.
    • Profit percent more than - das Drucken von Screenshots ist zeitintensiv, daher können Sie wählen, dass nur Screenshots gedruckt werden, deren Gewinn den Parameter übersteigt.
    • Only best results - wenn dies zutrifft, werden nur Screenshots mit dem besten Ergebnis jedes nutzerdefinierten Kriteriums gespeichert; andernfalls werden alle gespeichert.
    • Custom criterion - wenn alle Screenshots ausgewählt sind, kann dieser Parameter verwendet werden, um das nutzerdefinierte Kriterium festzulegen, nach dem die Screenshots im Ordner sortiert werden sollen.
    • ratio - das Gewicht zur Berechnung der nutzerdefinierten Kriterien Gewinn_R_Eigenkapital_r2, Gewinn_Eigenkapital_R_r2, Gewinn_Eigenkapital_r2_R.
    • Calculate criterion by - R^2 auf der Grundlage von Bilanz oder Kapitalwert berechnen; wirkt sich auf alle nutzerdefinierten Kriterien aus, bei denen R^2 verwendet wird.

             r2, profit_r2, profit_r2_Err, profit_R_equity_r2, profit_equity_R_r2, profit_equity_r2_R.

    • Graph - wählen Sie zwischen Graph wie im Tester "Rückwärts getrennt von Vorwärts", d.h. Vorwärts beginnt mit dem Startsaldo,  

    oder "Back continued by Forward" - Vorwärts beginnt mit dem letzten Saldo des Rückwärtsdurchlaufs.

    //---

    Die auf dieser Website veröffentlichten Artikel waren beim Schreiben des Programms sehr hilfreich.

    Ich möchte mich bei allen Autoren der hier erwähnten Artikel bedanken!


    Übersetzt aus dem Russischen von MetaQuotes Ltd.
    Originalartikel: https://www.mql5.com/ru/articles/9922

    Beigefügte Dateien |
    SkrShotOpt.mqh (17.56 KB)
    MQL5.zip (11.59 KB)
    Grafiken in der Bibliothek DoEasy (Teil 93): Vorbereiten der Funktionen zur Erstellung zusammengesetzter grafischer Objekte Grafiken in der Bibliothek DoEasy (Teil 93): Vorbereiten der Funktionen zur Erstellung zusammengesetzter grafischer Objekte
    In diesem Artikel beginne ich mit der Entwicklung der Funktionalität zur Erstellung von zusammengesetzten grafischen Objekten. Die Bibliothek wird die Erstellung von zusammengesetzten grafischen Objekten unterstützen, wobei diese Objekte eine beliebige Hierarchie von Verbindungen haben können. Ich werde alle notwendigen Klassen für die spätere Implementierung solcher Objekte vorbereiten.
    Grafiken in der DoEasy-Bibliothek (Teil 92): Speicherklasse der grafischen Standardobjekte. Änderungsverlauf der Objekteigenschaften Grafiken in der DoEasy-Bibliothek (Teil 92): Speicherklasse der grafischen Standardobjekte. Änderungsverlauf der Objekteigenschaften
    In diesem Artikel werde ich die Speicherklasse der grafischen Standardobjekte erstellen, die es dem Objekt ermöglicht, seine Zustände zu speichern, wenn seine Eigenschaften geändert werden. Dies wiederum ermöglicht den Rücksprung zu vorherigen Zuständen des grafischen Objekts.
    Lernen Sie, wie Sie ein Handelssystem mit Hilfe der Envelopes entwickelt Lernen Sie, wie Sie ein Handelssystem mit Hilfe der Envelopes entwickelt
    In diesem Artikel werde ich Ihnen eine der Methoden vorstellen, wie man mit Bändern handeln kann. Dieses Mal werden wir uns mit Envelopes (Hüllkurve) beschäftigen und sehen, wie einfach es ist, einige Strategien auf der Grundlage der Envelopes zu erstellen.
    Entwicklung eines Expert Advisor für den Handel von Grund auf Entwicklung eines Expert Advisor für den Handel von Grund auf
    In diesem Artikel werden wir besprechen, wie man einen Handelsroboter mit minimalem Programmieraufwand entwickelt.