English Русский 中文 Español 日本語 Português Türkçe
preview
Brute-Force-Ansatz zur Mustersuche (Teil IV): Minimale Funktionalität

Brute-Force-Ansatz zur Mustersuche (Teil IV): Minimale Funktionalität

MetaTrader 5Tester | 10 Mai 2021, 10:05
570 0
Evgeniy Ilin
Evgeniy Ilin
In diesem Artikel werde ich eine neue Version meines Produkts vorstellen, die das Mindeste leistet und in der Lage ist, Arbeitseinstellungen für den Handel zu extrahieren. Die Änderungen zielen vor allem darauf ab, die Nutzerfreundlichkeit zu erhöhen und die Mindesteinstiegsschwelle zu verbessern. Das Hauptziel ist nach wie vor, möglichst viele Nutzer für die Suche nach Einstellungen und Marktforschung zu gewinnen. Anstatt Details zum Handel zu besprechen, werde ich so viele nützliche Informationen wie möglich bereitstellen. Ich werde versuchen, die Anwendung dieser Methode nachvollziehbar zu erklären, Vor- und Nachteile zu beschreiben sowie die Aussichten für die praktische Anwendung zu betrachten.


Änderungen in der neuen Version

Wie im vorherigen Artikel wurde das Programm in Bezug auf Funktionalität und Nutzerfreundlichkeit erheblich verbessert. Frühere Versionen waren sehr umständlich, hatten verschiedene Bugs und Fehler. Diese Version enthält ein Maximum an Änderungen. Es wurde viel getan, um die Expert Advisor Vorlagen und die Software zu modernisieren. Liste der Änderungen:

  1. Neu gestaltete Schnittstelle
  2. Hinzugefügt wurde ein weiteres Polynom für die Brute-Force-Methode (basierend auf der überarbeiteten Fourier-Reihe)
  3. Verbesserter Mechanismus zur Generierung von Zufallszahlen
  4. Erweiterung des Methodenkonzepts in Richtung Komfort und Nutzerfreundlichkeit
  5. Mechanismus zur Variation der Losgrößen für ultrakurze Zeitspannen hinzugefügt
  6. Mechanismus zur Spread-Berechnung hinzugefügt
  7. Mechanismus zur Reduzierung des Rauschens des Spread hinzugefügt
  8. Fehlerbehebungen

Die meisten der bisher geplanten Änderungen sind implementiert worden. Weitere Änderungen am Algorithmus werden nicht so bedeutend sein.


Erste Demonstration und das neue Konzept

Beim Erstellen von EAs habe ich festgestellt, dass es eine Herausforderung sein kann, immer EA-Namen zu erstellen und mit zahlreichen Einstellungen umzugehen. Manchmal können sich die Namen überschneiden und die Einstellungen verwechselt werden. Die Lösung für dieses Problem ist ein Expert Advisor, der Einstellungen erhält. Das Programm kann eine Konfigurationsdatei im üblichen txt-Format erzeugen, und der Expert Advisor liest sie ein. Dieser Ansatz beschleunigt die Arbeit mit dieser Lösung und macht sie einfacher und verständlicher. Das Lösungsschema sieht nun wie folgt aus:

Forex Awaiter Verwendung


Natürlich ist die Version des Programms, die Handelsroboter generiert, immer noch verfügbar. Aber in diesem Artikel wird ein neues Konzept vorgestellt, das speziell für die Terminals MetaTrader 4 und MetaTrader 5 entwickelt wurde - es ermöglicht normalen Nutzern, diese Lösung so einfach und schnell wie möglich zu nutzen. Das Praktischste an dieser Lösung ist, dass die Einstellung sowohl im MetaTrader 4 als auch im MetaTrader 5 gleich funktioniert.

Ich werde nur einen Teil des neuen Interfaces zeigen, weil es ziemlich groß ist und zu viel Platz im Artikel einnehmen würde. Hier ist die erste Registerkarte.

Neues Awaiter-Interface


Alle Elemente sind zur einfachen Navigation in entsprechende Abschnitte unterteilt. Nicht verwendete Elemente werden gesperrt, wenn ihre Verwendung sinnlos ist.

Ich habe ein spezielles Video erstellt, in dem die Bedienung des Programms demonstriert wird.



Warum ist es nicht möglich, Einstellungen über gewöhnliche Set-Dateien zu übergeben? Der Grund ist, dass Set-Dateien keine Arrays enthalten können, während diese Methode Arrays als Eingänge verwendet, die eine gleitende Länge haben. In diesem Fall ist die bequemste Lösung, gewöhnliche Textdateien zu verwenden.


Neues Polynom auf Basis modifizierter Fourier-Reihen

Viele Personen, die sich mit maschinellem Lernen beschäftigen, verwenden die Fourier-Reihe aktiv für ihre Algorithmen für verschiedene Zwecke. Die Fourier-Reihe wurde ursprünglich geschaffen, um eine Funktion in eine Reihe im Bereich [-π;π] zu zerlegen. Was wir wissen müssen, ist, wie die Funktion in eine Reihe zerlegt werden kann und ob eine solche Zerlegung notwendig ist. Außerdem ist es wichtig, die Besonderheiten der Variablenersetzungsmethode zu kennen, da die Zerlegung auf einem anderen Intervall als [-π;π] durchgeführt werden kann. All dies erfordert gute Kenntnisse der Mathematik, ebenso wie das Verständnis, ob es einen Sinn hat, sie für den Handel zu verwenden. Die allgemeine Ansicht der Fourier-Reihe ist wie folgt:

Das Aussehen der allgemeinen Form der Fourier-Reihe

In dieser Form kann dieses Polynom nur für die Darstellung von Handelsmustern in einer bequemeren Form nützlich sein, sowie für den Versuch, die Preisbewegung vorherzusagen, unter der Annahme, dass der Preis ein Wellenprozess ist. Diese Annahme scheint gültig zu sein, aber in dieser Form ist es für Brute-Force unanwendbar, da wir in diesem Fall das gesamte Konzept der Methode radikal ändern müssten. Stattdessen müssen wir das Polynom so umformen, dass es für diese Methode angewendet werden kann. Es kann eine Menge Variationen geben. Ich schlage jedoch vor, wie folgt vorzugehen, um die Formel nahe an der Fourier-Reihe zu halten, ohne die Formel für ihren eigentlichen Zweck zu verwenden:

Erste Umformung

Hier hat sich nichts geändert, außer dass die Reihe mehr Freiheiten hat: Ihre Periode ist sowohl im Plus als auch im Minus fließend. Außerdem gibt es jetzt eine endliche Anzahl von Termen. Das liegt daran, dass das Array C[] Koeffizienten enthält, die wir kombinieren werden, um eine passende Formel zu finden. Die Anzahl solcher Koeffizienten ist begrenzt. Diese Reihe kann nicht unendlich sein, also müssen wir sie auf "m" Balken beschränken. Außerdem habe ich den ersten Term entfernt, um die Symmetrie zu erhalten - die Werte der Formel sollten möglichst symmetrische Signale in den Bereichen "+" und "-" ergeben. Aber dieser Weg kann nur für die Auswahl einer Funktion in Abhängigkeit von 1 Balken verwendet werden! Wir müssen sicherstellen, dass alle Balkenwerte in der Formel vorhanden sind. Außerdem hat ein Balken 6 Parameter, nicht 1. Diese 6 Parameter wurden im zweiten Artikel dieser Serie betrachtet. Hier müssen wir einen Balken der Verarbeitungsgenauigkeit opfern, um alle restlichen Balken zu berücksichtigen. Idealerweise sollte dieser Betrag in einen anderen verpackt werden. Aber ich möchte das Polynom nicht verkomplizieren und deshalb werden wir vorerst seine einfachste Version verwenden:

Endgültige Polynom

In der Tat ist aus einer eindimensionalen Funktion eine mehrdimensionale geworden. Das bedeutet aber nicht, dass dieses Polynom jede mehrdimensionale Funktion in dem gewählten mehrdimensionalen Hyperwürfel beschreiben kann. Wie auch immer, diese Funktion liefert eine weitere Familie von mehrdimensionalen Funktionen, die andere Regelmäßigkeiten beschreiben können, die durch die Taylor-Reihe nicht richtig abgedeckt werden können. Dies bietet mehr Chancen, ein besseres Muster für dieselbe Datenprobe zu finden. 

Im Code wird diese Funktion wie folgt aussehen:

if ( Method == "FOURIER" )
   {
      for ( int i=0; i<CNum; i++ )
         {
         Val+=C1[iterator]*MathSin(C1[iterator+1]*(Close[i+1]-Open[i+1])/_Point)+C1[iterator+2]*MathCos(C1[iterator+3]*(Close[i+1]-Open[i+1])/_Point);
         iterator+=4;
         }

      for ( int i=0; i<CNum; i++ )
         {
         Val+=C1[iterator]*MathSin(C1[iterator+1]*(High[i+1]-Open[i+1])/_Point)+C1[iterator+2]*MathCos(C1[iterator+3]*(High[i+1]-Open[i+1])/_Point);
         iterator+=4;
         }

      for ( int i=0; i<CNum; i++ )
         {
         Val+=C1[iterator]*MathSin(C1[iterator+1]*(Open[i+1]-Low[i+1])/_Point)+C1[iterator+2]*MathCos(C1[iterator+3]*(Open[i+1]-Low[i+1])/_Point);
         iterator+=4;
         }

      for ( int i=0; i<CNum; i++ )
         {
         Val+=C1[iterator]*MathSin(C1[iterator+1]*(High[i+1]-Close[i+1])/_Point)+C1[iterator+2]*MathCos(C1[iterator+3]*(High[i+1]-Close[i+1])/_Point);
         iterator+=4;
         }

      for ( int i=0; i<CNum; i++ )
         {
         Val+=C1[iterator]*MathSin(C1[iterator+1]*(Close[i+1]-Low[i+1])/_Point)+C1[iterator+2]*MathCos(C1[iterator+3]*(Close[i+1]-Low[i+1])/_Point);
         iterator+=4;
         }         

   return Val;
   }

Es ist nicht die ganze Funktion, sondern nur ihr Teil, der das Polynom implementiert. Trotz der Einfachheit sind auch solche Formeln an den Markt anpassbar.

Ich habe eine sehr schnelle Brute-Force für historische Daten für das letzte Jahr mit dieser Methode durchgeführt, um zu zeigen, dass diese Formel funktionieren kann. Allerdings müssen wir noch herausfinden, ob sie gut funktioniert oder nicht. Tatsächlich ist es mir noch nicht gelungen, mit dieser Formel eine wirklich effiziente Lösung zu finden. Aber ich denke, das ist eine Frage der Zeit und der Rechenkapazitäten. Ich habe viel Zeit damit verbracht, mit der ersten Version zu arbeiten. Das ist das Ergebnis, das ich für USDJPY M15 für das letzte Geschichtsjahr erreichen konnte:

FOURIER-Methode


Was mir an dieser Formel nicht gefallen hat, ist, dass sie sehr instabil ist, was die Unterdrückung des Rauschens des Spreads angeht. Vielleicht hängt das mit den Besonderheiten der harmonischen Funktionen im Rahmen dieser Methode zusammen. Vielleicht habe ich die Formel auch nicht ganz korrekt formuliert. Achten Sie darauf, dass Sie die Option "Spread Control" im zweiten Reiter aktivieren. Dadurch wird der Mechanismus zur Unterdrückung des Rauschens des Spreads während der Optimierung ausgeschaltet und es entstehen recht gute Varianten. Wahrscheinlich ist die Formel sehr "sanft". Dennoch ist sie in der Lage, recht gute Varianten zu finden.


Über die Software-Implementierung von innen

Diese Fragen wurden in meinen bisherigen Artikeln kaum behandelt. Ich habe mich entschlossen, diesen Teil zu enthüllen, um zu zeigen, wie es von innen funktioniert. Der interessanteste und einfachste Teil ist die Generierung von Koeffizienten für die Formel. Die Erklärung dieses Teils kann Ihnen helfen zu verstehen, wie die Koeffizienten generiert werden:

public void GenerateC(Tester CoreWorker)
   {
   double RX;
   TYPE_RANDOM RT;
   RX = RandomX.NextDouble();
   if (RandomType == TYPE_RANDOM.RANDOM_TYPE_R) RT = (TYPE_RANDOM)RandomX.Next(0, Enum.GetValues(typeof(TYPE_RANDOM)).Length-1);
   else RT = RandomType;

   for (int i = 0; i < CoreWorker.Variant.ANum; i++)
      {
      if (RT == TYPE_RANDOM.RANDOM_TYPE_0) 
         {
         if (i > 0) CoreWorker.Variant.Ci[i] = CoreWorker.Variant.Ci[i-1]*RandomX.NextDouble();
         else CoreWorker.Variant.Ci[0]=1.0;
         }
      if (RT == TYPE_RANDOM.RANDOM_TYPE_5)
         {
         if (RandomX.NextDouble() >= 0.5)
            {
            if (i > 0) CoreWorker.Variant.Ci[i] = CoreWorker.Variant.Ci[i - 1] * RandomX.NextDouble();
            else CoreWorker.Variant.Ci[0] = 1.0;
            }
         else
            {
            if (i > 0) CoreWorker.Variant.Ci[i] = CoreWorker.Variant.Ci[i - 1] * (-RandomX.NextDouble());
            else CoreWorker.Variant.Ci[0] = -1.0;
            }
         }
      if (RT == TYPE_RANDOM.RANDOM_TYPE_1) CoreWorker.Variant.Ci[i] = RandomX.NextDouble();
      if (RT == TYPE_RANDOM.RANDOM_TYPE_2)
         {
         if (RandomX.NextDouble() >= 0.5) CoreWorker.Variant.Ci[i] = RandomX.NextDouble();
         else CoreWorker.Variant.Ci[i] = -RandomX.NextDouble();
         }
      if (RT == TYPE_RANDOM.RANDOM_TYPE_3)
         {
         if (RandomX.NextDouble() >= RX)
            {
            if (RandomX.NextDouble() >= RX + (1.0 - RX) / 2.0) CoreWorker.Variant.Ci[i] = RandomX.NextDouble();
            else CoreWorker.Variant.Ci[i] = -RandomX.NextDouble();
            }
         else CoreWorker.Variant.Ci[i] = 0.0;
         }
      if (RT == TYPE_RANDOM.RANDOM_TYPE_4)
         {
         if (RandomX.NextDouble() >= RX) CoreWorker.Variant.Ci[i] = RandomX.NextDouble();
         else CoreWorker.Variant.Ci[i] = 0.0;
         }
      }
   }

Es ist ziemlich einfach: Es gibt mehrere feste Typen der Zufallszahlengenerierung und es gibt einen allgemeinen Typ, der alles auf einmal implementiert. Jeder der Generierungstypen wurde in der Praxis getestet. Es stellte sich heraus, dass der allgemeine Generierungstyp "RANDOM_TYPE_R" die maximale Effizienz aufweist. Feste Typen liefern aufgrund der unterschiedlichen Beschaffenheit von Kursen auf verschiedenen Instrumenten und Zeitrahmen nicht immer ein Ergebnis. Visuell sind diese Unterschiede in den meisten Fällen nicht zu sehen, aber die Maschine sieht alles. Obwohl einige feste Typen auf einigen Zeitrahmen mehr Signale mit maximaler Qualität liefern können. Mir ist aufgefallen, dass z.B. bei NZDUSD H1 die Qualität der Ergebnisse stark ansteigt, wenn man RANDOM_TYPE_4 verwendet, was "nur Nullen und positive Zahlen" bedeutet. Dies kann ein deutlicher Hinweis auf versteckte, dem Auge unzugängliche Wellenprozesse sein. Ich würde gerne verschiedene Instrumente genauer untersuchen, aber es ist schwer, das alleine zu tun.


Neuer Mechanismus zur Bekämpfung des variierenden Spreads und zur Berücksichtigung des Spreads

Wie im vorigen Artikel erwähnt, verzerrt der Spread die Kursdaten, so dass die meisten der gefundenen Muster meist innerhalb des Spreads liegen. Der Spread ist der schlimmste Feind jeder Strategie, weil die meisten Strategien keine ausreichende mathematische Erwartung bieten, um den Spread zu decken. Sie sollten sich nicht von einem Backtest oder einer positiven Handelsstatistik auf einem realen Konto einen Monat oder sogar ein Jahr lang täuschen lassen, da diese Datenstichprobe zu klein ist, um die zukünftige Performance zu beurteilen. Es gibt eine eigene Klasse von Handelsstrategien und automatisierten Handelssystemen, die "Night Scalpers" genannt werden. Diese Roboter fangen kleine Gewinne in einer begrenzten Zeitspanne. Broker gehen aktiv gegen solche Systeme vor, indem sie die Spreads nach Mitternacht ausweiten. Der Spread wird auf ein solches Niveau gesetzt, dass die meisten Strategien unrentabel werden.

Es gibt einen Wert, der bei den meisten Brokern fast gleich ist:

  • Spread = (Ask - Bid) / _Point
  • MidPrice = ( Ask + Bid ) / 2

Dieser Preis ist grün hinterlegt. Dies ist die Mitte des Orderbuchs. Das Orderbuch ist in der Regel relativ zu diesem Preis aufgereiht, und beide Preise befinden sich in gleichem Abstand zu diesem Preis. Betrachtet man die klassische Definition des Orderbuchs, so macht dieser Preis eigentlich keinen Sinn, da es keine Handelsaufträge gibt. Selbst wenn wir davon ausgehen, dass alle Broker ihre eigenen Spreads haben, kann dieser Preis bei allen Brokern fast gleich sein. Hier ist ein Diagramm:

Spread

Die obere Abbildung zeigt die Preisreihen von zwei zufällig ausgewählten Brokern. Es gibt immer einen "Ask"-Preis und einen "Bid"-Preis, die Kauf- und Verkaufspreise symbolisieren. Die schwarze Linie ist für beide Preisreihen die gleiche. Dieser Preis kann leicht berechnet werden, wie ich oben gezeigt habe. Das Wichtigste ist, dass dieser Wert praktisch nicht von erweiterten oder verengten Spreads eines bestimmten Brokers abhängt, da alle Änderungen fast gleichmäßig sind, bezogen auf einen bestimmten Preis.

Die untere Zahl zeigt eine reale Situation, die tatsächlich mit Kursen von verschiedenen Brokern vorkommt. Die Sache ist, dass sogar dieser Durchschnittspreis in verschiedenen Streams unterschiedlich ist. Ich kenne die Gründe dafür nicht. Selbst wenn ich es wüsste, wäre das für den Handel kaum von Nutzen. Ich entdeckte diese Tatsache, als ich Arbitrage-Handel praktizierte, wo all diese Nuancen extrem wichtig sind. In Bezug auf unsere Methode ist es nur wichtig:

  • MidPrice1=f(t)
  • MidPrice2=MidPrice1-D
  • MidPrice1 '(t) =  MidPrice2 '(t)

Mit anderen Worten: Der Durchschnittspreis beider Preisreihen (wenn als Zeitfunktionen dargestellt) hat die gleiche Ableitung, da sich diese Funktionen nur in der Konstante "D" unterscheiden. Da unser Polynom nicht die Preise, sondern deren Differenz verwendet, sind alle Werte Ableitungsfunktionen dieser Durchschnittspreisfunktionen. Da diese Ableitungen für alle Broker gleich sind, können wir erwarten, dass die Einstellungen bei verschiedenen Brokern effizient sein können. Im anderen Fall haben die gefundenen Einstellungen eine extrem geringe Chance auf einen erfolgreichen Backtest auf realen Ticks oder auf Anwendbarkeit bei anderen Brokern. Das obige Konzept vermeidet solche Probleme.

Um diesen Mechanismus zu implementieren, musste ich entsprechende Modifikationen an allen Elementen vornehmen. Zunächst ist es notwendig, beim Schreiben der Kursdatei die Spreads an allen wichtigen Taktpunkten zu erfassen. Dies sind die Punkte von Open[], Close[], High[], Low[]. Der Spread wird verwendet, um die Werte anzupassen und somit den Ask-Preis zu erhalten, da die Balken auf Bid-Preisen basieren. Die EAs, die Kurse schreiben, basieren nun auf Ticks und nicht auf Balken. Die Funktion zum Erfassen dieser Balken sieht nun wie folgt aus:

void WriteBar()
   {
   FileWriteString(Handle0x,"\r\n");
   FileWriteString(Handle0x,DoubleToString(Close[1],8)+"\r\n");
   FileWriteString(Handle0x,DoubleToString(Open[1],8)+"\r\n");
   FileWriteString(Handle0x,DoubleToString(High[1],8)+"\r\n");
   FileWriteString(Handle0x,DoubleToString(Low[1],8)+"\r\n");         
   FileWriteString(Handle0x,IntegerToString(int(Time[1]))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(PrevSpread)+"\r\n");
   FileWriteString(Handle0x,IntegerToString(CurrentSpread)+"\r\n");
   FileWriteString(Handle0x,IntegerToString(PrevHighSpread)+"\r\n");
   FileWriteString(Handle0x,IntegerToString(PrevLowSpread)+"\r\n");   
   MqlDateTime T;
   TimeToStruct(Time[1],T);
   FileWriteString(Handle0x,IntegerToString(int(T.hour))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.min))+"\r\n");
   FileWriteString(Handle0x,IntegerToString(int(T.day_of_week))+"\r\n");         
   }      

Vier Linien sind grün hervorgehoben - diese Linien erfassen die Spanne an allen vier Punkten des Balkens. In der vorherigen Version wurden diese Werte nicht aufgezeichnet und bei den Berechnungen nicht berücksichtigt. Diese Daten können leicht ermittelt und aufgezeichnet werden. Die folgende einfache Tick-basierte Funktion wird verwendet, um den Spread bei High und Low zu erhalten:

void RecalcHighLowSpreads()
   {
   if ( Close[0] > LastHigh )
      {
      LastHigh=Close[0];
      HighSpread=int(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD));
      }
   if ( Close[0] < LastLow )
      {
      LastLow=Close[0];
      LowSpread=int(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD));
      }      
   }

Diese Funktion ermittelt nur den Spread am höchsten und tiefsten Punkt des Balkens, während der aktuelle Balken gebildet wird. Wenn ein neuer Balken erscheint, wird der aktuelle Balken als vollständig gebildet betrachtet und seine Daten werden in die Datei geschrieben. Diese Funktion arbeitet im Tandem mit einer anderen bar-basierten Funktion:

bool bNewBar()
   {
   ArraySetAsSeries(Close,false);                        
   ArraySetAsSeries(Open,false);                           
   ArraySetAsSeries(High,false);                        
   ArraySetAsSeries(Low,false);                              
   CopyOpen(_Symbol,_Period,0,2,Open);
   CopyClose(_Symbol,_Period,0,2,Close);
   CopyHigh(_Symbol,_Period,0,2,High);
   CopyLow(_Symbol,_Period,0,2,Low);
   ArraySetAsSeries(Close,true);                        
   ArraySetAsSeries(Open,true);                           
   ArraySetAsSeries(High,true);                        
   ArraySetAsSeries(Low,true);                                 
   if ( Time0 < Time[1] )
      {
      if (Time0 != 0)
         {
         Time0=Time[1];
         PrevHighSpread=HighSpread;
         PrevLowSpread=LowSpread;         
         PrevSpread=CurrentSpread;
         CurrentSpread=int(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD));
         HighSpread=CurrentSpread;
         LowSpread=CurrentSpread;         
         return true;
         }
      else
         {
         Time0=Time[1];
         return false;
         }
      }
   else return false;
   }

Die Funktion ist sowohl ein Prädikat als auch ein wichtiges Element der Logik, in der alle vier Spreizungen endgültig festgelegt werden. Sie ist im Programm entsprechend implementiert. Sie arbeitet sehr einfach in OnTick():

RecalcHighLowSpreads();
if ( bNewBar()) WriteBar();

Die Angebotsdatei enthält die folgenden Daten:

Struktur der Balken

Ein Array mit Durchschnittspreisen ist im Programm identisch implementiert:

OpenX[1]=Open[1]+(double(PrevSpread)/2.0)*_Point;
CloseX[1]=Close[1]+(double(Spread)/2.0)*_Point;
HighX[1]=High[1]+(double(PrevHighSpread)/2.0)*_Point;
LowX[1]=Low[1]+(double(PrevLowSpread)/2.0)*_Point;

Es mag den Anschein haben, dass sich mit diesem Ansatz die Unterdrückung des Rauschens des Spreads realisieren lässt. Das Problem ist jedoch, dass es eine bestimmte Anzahl von gesammelten Ticks erfordert (je höher der Zeitrahmen, desto mehr Ticks werden pro Balken benötigt). Das Sammeln einer großen Anzahl von Ticks erfordert viel Zeit. Außerdem speichern die Balken keine Ask-Preise oder Spread-Werte, deshalb haben wir Bid für die Berechnungen verwendet.

Als zusätzliche Option habe ich einen Mechanismus hinzugefügt, um den Spread bei der Optimierung der Ergebnisse zu berücksichtigen. Tests haben gezeigt, dass dieser Mechanismus optional ist, aber bei ausreichender Rechenleistung recht gute Ergebnisse liefern kann. Es geht darum, den Algorithmus zu veranlassen, Positionen nur dann zu öffnen und zu schließen, wenn der Spread den gewünschten Wert nicht überschreitet. In der aktuellen Version werden die Spreads in den Balkendaten aufgezeichnet, so dass der Wert kontrolliert werden kann, was uns erlaubt, die wahren Testwerte ohne den Spread zu berechnen.


Kurzfristige Losgrößenvariante

Die Losgrößenvariante (eine der Varianten) kann nicht nur auf sehr kurzen Abschnitten, sondern auch auf langen Abschnitten verwendet werden. Es gibt zwei Mechanismen zur Steuerung von Risiko und Losgröße.

  • Erhöhen der Losgrößen
  • Vermindern der Losgröße

Der erste Mechanismus sollte im invertierten Handel verwendet werden, wenn wir erwarten, dass das Signal bald invertiert wird. Die zweite Option funktioniert nur, wenn bekannt ist, dass das Signal stabil ist und lange andauern wird. Sie können beliebige Kontrollfunktionen verwenden. Ich habe die einfachsten verwendet — lineare Funktionen. Mit anderen Worten, viele ändern sich mit der Zeit. Schauen wir uns die Funktionsweise des Mechanismus in der Praxis an.

Dies wird anhand des USDJPY M15-Kurses geschehen. In diesem Fall verwende ich die MQL4-Version des Roboters zur Demonstration, da der Roboter bei Backtests auf realen Ticks versagt, da er an Punkten mit erhöhter Streuung handelt. Ich wollte beweisen, dass ein guter Brute-Force-Ansatz auf niedrigen Zeitrahmen gute Vorwärts-Ergebnisse in einem Intervall liefern kann, die der Brute-Force-Periode gleichen oder besser sind. Die Arbeit war nicht sehr umfangreich, da meine Rechnerkapazitäten begrenzt sind. Aber dieses Ergebnis reicht völlig aus, um eine ziemlich lange funktionierende Vorwärtsperiode zu demonstrieren, mit zwei Mechanismen zu Losgrößenverwaltung in diesen Vorwärtszeitspanne. Lassen Sie uns mit einer Demonstration der gefundenen Variante im Suchintervall beginnen. Das Intervall ist gleich einem Jahr:

USDJPY M15 Brute-Force-Bereich aus der Historie

Die mathematische Erwartung liegt hier bei etwas mehr als 12 Punkten (diesmal ohne Spread, was für uns nicht wichtig ist, da wir den Spread diesmal ignorieren). Schauen wir uns den Gewinnfaktor an. Ein Ein-Jahres-Test in der Zukunft sieht wie folgt aus:

1 Jahr in die Zukunft

Trotz der Tatsache, dass die Suche nur ein Jahr dauerte, hat sie nicht weniger als ein Jahr lang funktioniert. In der Praxis bedeutet dies, dass Sie, wenn Sie einen guten Computer haben, alle wichtigen Währungspaare mit niedrigen Spreads in ein oder zwei Wochen analysieren können, dann die besten von ihnen auswählen und das Muster für mindestens ein weiteres Jahr ausnutzen. Vergessen Sie nicht, dafür zu sorgen, dass das System einen Backtest mit realen Ticks im MetaTrader 5 besteht. Um noch mehr Beweise zu sammeln, ist es eine gute Idee, ein kleines Team zu bilden, um die Daten auf mehreren Computern zu analysieren, um Ergebnisse zu sammeln und Statistiken zu erstellen.

Schauen wir uns nun den Vorwärtstest an - am Anfang gibt es einen sehr großen Drawdown, was normalerweise sehr üblich ist, wenn wir nach Mustern in kleinen Zeiträumen wie einem Jahr suchen. Dennoch kann auch dieser Drawdown für Ihre Zwecke genutzt werden. Dieses Intervall wird in der obigen Grafik in einem roten Rahmen dargestellt. Die EA-Arbeitszeit ist hier in den Einstellungen auf 50 Handelstage begrenzt (Samstag und Sonntag werden nicht mitgezählt). Außerdem wurde das Signal invertiert, um den Drawdown in einen Gewinn zu verwandeln. Dies wird gemacht, um den Teil des Graphen nach dem Drawdown abzuschneiden, da dieser nach der Invertierung in einen negativen umschlagen würde. Der resultierende Backtest sieht wie folgt aus:

umkehren + feste Losgröße, 50 Tage in die Zukunft

Schauen Sie auf den Gewinnfaktor. Wir haben ihn erhöht. Man weiß nie wirklich, ob es eine Umkehrung geben wird und wie weit diese Umkehrung gehen wird, aber sie rollt normalerweise um einen ziemlich großen Teil der Bewegung zurück, die im Brute-Force-Segment aufgetreten ist. Indem wir eine lineare Erhöhung des Lots vom Minimum zum Maximum anwenden, gelingt durch solch einen Backtest eine Erhöhung des Gewinnfaktors:

umkehren + steigende Losgröße, 50 Tage in die Zukunft

Sehen wir uns nun den umgekehrten Mechanismus an, der im einjährigen Vorwärtstest in einem grünen Rahmen dargestellt wird. Dieser Teil zeigt einen großen wachsenden Teil, gefolgt von einer Musterumkehr. In dieser Situation werden wir die Losgröße verringern. Der Roboter ist so eingestellt, dass er bis zum Ende dieses Segments handelt. Testen wir ihn zunächst mit einer festen Losgröße, um die Ergebnisse später vergleichen zu können:

Grüne Box, feste Losgröße

Lassen Sie uns nun den Mechanismus aktivieren, der eine Verringerung der Losgröße im Laufe der Zeit implementiert. Dies führt ebenfalls zu einer Erhöhung des Gewinnfaktors, während der Graph glatter wird und es am Ende keinen Drawdown gibt:

Grüne Box + Verringerung der Losgröße

Viele Verkäufer im Market verwenden diese Techniken. Wenn sie zur richtigen Zeit und an der richtigen Stelle eingesetzt werden, können sie sowohl die Rentabilität erhöhen als auch die Verluste reduzieren. In der Praxis ist es allerdings etwas komplizierter. Wie auch immer, diese Mechanismen sind in den von meinem Programm generierten Expert Advisors enthalten, sodass Sie sie bei Bedarf aktivieren und deaktivieren können.


Arbeitsvarianten für die globale Historie

Ich denke, dass viele Leser daran interessiert wären, zumindest einige funktionierende Einstellungsvarianten zu sehen und zu überprüfen, die in der Lage sind, globale Backtests mit echter Tick-Historie zu bestehen. Ich habe solche Einstellungen gefunden. Aufgrund der begrenzten Rechenkapazitäten und weil ich nur mit Brute-Force arbeite, hat die Suche ziemlich lange gedauert. Trotzdem habe ich einige Varianten gefunden. Hier sind sie:

USDCAD H1 2010-2020

USDJPY H1 2017-2021

EURUSD H1 2010-2021

Diese Einstellungen sind unten angehängt, so dass Sie sie testen können, wenn Sie möchten. Sie können auch versuchen, Ihre eigenen Einstellungen zu finden und sie auf Demokonten zu testen. Ab dieser Version des Programms kann jeder diese Methode ausprobieren.


Brute-Force-Mathematik

Dieser Abschnitt bedarf einer ausführlichen Erläuterung, damit der Nutzer die zugrunde liegenden Berechnungsprinzipien versteht. Beginnen wir damit, wie die erste Registerkarte des Programms funktioniert und wie die Ergebnisse zu interpretieren sind.

Brute-Force in der ersten Registerkarte

Tatsächlich ist alles, was in einem Brute-Force-Algorithmus passiert, immer mit der Wahrscheinlichkeitstheorie verbunden, denn wir haben immer eine Art Modell und eine Iteration. Eine Iteration ist hier ein voller Zyklus der Analyse der aktuellen Strategievariante. Ein voller Zyklus kann aus einem Test oder aus mehreren Tests bestehen, je nach dem spezifischen Ansatz. Die Anzahl der Tests und anderer Manipulationen ist nicht so wichtig, da alles als eine Iteration eingestuft werden kann. Eine Iteration kann als erfolgreich oder nicht erfolgreich angesehen werden, je nach den Anforderungen an das Ergebnis. Als Kriterien für ein gutes Ergebnis können verschiedenste quantitative Werte dienen, die von der Analysemethode abhängen.

Der Algorithmus gibt immer eine oder mehrere Varianten aus, die unseren Anforderungen entsprechen. Wir können dem Algorithmus vorgeben, wie viele Ergebnisse im Speicher abgelegt werden sollen. Alle anderen Ergebnisse, die die Anforderung erfüllen, aber nicht in den Speicher passen, werden verworfen. Egal wie viele Schritte es in unserem Brute-Force-Algorithmus gibt, dieser Prozess wird immer stattfinden. Andernfalls würden wir zu viel Zeit mit der Verarbeitung absichtlich minderwertiger Daten verschwenden. Auf diese Weise geht zwar keine einzige Variante verloren, aber das kann die Suchgeschwindigkeit verringern. Welchen Ansatz Sie wählen, bleibt letztlich Ihnen überlassen.

Lassen Sie uns nun direkt zum Punkt kommen. Jeder Ergebnis-Suchprozess läuft letztlich auf einen Prozess unabhängiger Tests nach dem Bernoulli-Schema hinaus (vorausgesetzt, der Algorithmus ist fixiert). Es hängt alles von der Wahrscheinlichkeit ab, eine gute Variante zu erhalten. Diese Wahrscheinlichkeit ist für einen festen Algorithmus immer konstant. In unserem Fall hängt diese Wahrscheinlichkeit von den folgenden Werten ab:

  • Stichprobengröße
  • Variabilität des Algorithmus
  • Nähe zur Basis
  • Striktheit der Anforderungen an das Endergebnis

Dabei wachsen die Quantität und die Qualität der erzielten Ergebnisse mit der Anzahl der Iterationen, gemäß der Bernoulli-Formel. Vergessen Sie aber nicht, dass es sich hierbei um einen rein probabilistischen Prozess handelt! Das heißt, es ist unmöglich, mit Sicherheit vorherzusagen, welche Ergebnisse Sie erhalten werden. Es ist nur möglich, die Wahrscheinlichkeit zu berechnen, das gewünschte Ergebnis zu finden

  • Pk - die Wahrscheinlichkeit, dass die Iteration eine funktionierende Variante mit den spezifizierten Anforderungen erzeugt (diese Wahrscheinlichkeit kann je nach Anforderungen stark variieren)
  • C(n,m) - die Anzahl der Kombinationen von "n" zu "m"
  • Pa=Sum(m0...m...n)[C(n,m)*Pow(Pk ,m)*Pow(1-Pk ,n-m)] - die Wahrscheinlichkeit, dass wir nach n Iterationen mindestens m0 primäre Varianten haben, die unsere Anforderungen erfüllen
  • m0 — minimale Anzahl von zufriedenstellenden Prototypen
  • Pa — Wahrscheinlichkeit des Erhalts von mindestens "m0" oder mehr aus "n" Iterationen
  • n — maximal verfügbare Anzahl von Zyklen der Suche nach funktionierenden Prototypen (wie lange sind wir bereit, auf die Ergebnisse zu warten)

Die Anzahl der Zyklen kann auch in Zeit ausgedrückt werden: Nehmen Sie die Geschwindigkeit der Brute-Force aus dem Zähler in der ersten Registerkarte und die Zeit, die Sie bereit sind, für die Verarbeitung der aktuellen Daten zu verwenden:

  • Sh - Geschwindigkeit in Form der Anzahl von Iterationen pro Stunde
  • T - Zeit in Stunden, die wir bereit sind zu warten
  • n = Sh*T

In ähnlicher Weise ist es möglich, die Wahrscheinlichkeit zu berechnen, Varianten zu finden, die bestimmten Qualitätsanforderungen genügen. Die obigen Formeln erlauben es, Varianten zu finden, die unter den Filter Abweichung ("Deviation") fallen, der eine Voraussetzung für die Linearität des Ergebnisses ist. Wenn dieser Filter nicht aktiviert ist, wird jede Iteration als erfolgreich angesehen und es werden immer Varianten gefunden. Gefundene Varianten werden nach der Qualitätsbewertung sortiert. Abhängig von der benötigten Qualität ist der "Ps"-Wert eine Funktion des Qualitätswertes, der modulo genommen wird. Je höher die benötigte Qualität ist, desto niedriger ist der Wert dieser Funktion:

  • Ps - die Wahrscheinlichkeit, ein Ergebnis mit bestimmten zusätzlichen Qualitätsanforderungen zu finden
  • q - die geforderte Qualität
  • qMax - die höchste verfügbare Qualität
  • Ps = Ps(|q|) = K * Px(|q|) , q <= qMax
  • K = Pk - dieser Koeffizient berücksichtigt die Wahrscheinlichkeit, eine zufällige Variante zu erhalten (qualitätsbezogene Varianten werden aus solchen Varianten ausgewählt)
  • Ps ' (|q|) < 0
  • Lim (q-->qMax) [  Ps(|q|) ] = 0

Die erste Ableitung dieser Funktion ist negativ und symbolisiert, dass die Wahrscheinlichkeit, die Anforderungen zu erfüllen, gegen Null tendiert, wenn die Anforderungen steigen. Wenn "q" gegen den maximal verfügbaren Wert tendiert, tendiert der Wert dieser Funktion gegen "0", da es sich um eine Wahrscheinlichkeit handelt. Wenn "q" größer als der Maximalwert ist, ist diese Funktion bedeutungslos, da der ausgewählte Algorithmus nicht jede höhere Qualität erreichen kann. Diese Funktion ergibt sich aus der Wahrscheinlichkeitsdichtefunktion einer Zufallsvariablen "q". Die folgende Abbildung zeigt Ps(q) und die Wahrscheinlichkeitsdichte der Zufallsvariablen P(q), sowie weitere wichtige Größen:

Variabilität

Basierend auf diesen Abbildungen:

  • Integral(q0,qMax) [P(q)] = Integral(-qMax,-q0) [P(q)] =  K*Px(|q|) = Ps(|q|)  - das ist die Wahrscheinlichkeit, dass in der aktuellen Iteration eine Variante mit |q| zwischen q0 und qMax gefunden wird.
  • Integral(q1,q2) [P(q)] - die Wahrscheinlichkeit, dass als Ergebnis der Iteration ein Qualitätswert zwischen q1 und q2 gefunden wird (dies ist ein Beispiel für die Interpretation der Verteilungsfunktion der Zufallsvariablen)

Je mehr Qualität wir also wollen, desto mehr Zeit müssen wir aufwenden und desto weniger Varianten werden gefunden. Darüber hinaus hat jede Methode eine Obergrenze für den Qualitätswert, die sowohl von den Daten, die wir analysieren, als auch von der Perfektion unserer Methode abhängt.

Optimierung in der zweiten Registerkarte

Der Optimierungsprozess in der zweiten Registerkarte unterscheidet sich etwas vom primären Suchprozess. Er verwendet aber immer noch eine Iteration und die Wahrscheinlichkeit, eine Variante zu erhalten, die unseren Anforderungen entspricht. Diese Registerkarte hat viel mehr Filter, dementsprechend ist die Wahrscheinlichkeit, gute Ergebnisse zu erhalten, geringer. Da die zweite Registerkarte jedoch bereits verarbeitete Varianten verbessert, werden auf der zweiten Registerkarte umso bessere Ergebnisse erzielt, je besser die auf der ersten Registerkarte gefundenen Optionen sind. Die endgültige Formel, nach der eine bestimmte Variante verbessert wird, ähnelt ein wenig der Bernoulli-Gleichung. Was uns interessiert, ist die Wahrscheinlichkeit, mindestens eine Variante zu erhalten, die unserem Filter entspricht. Die Erklärung ist wie folgt:

  • Py = Sum(1...m...n)[ Sum(0... i ... C(n,m)-1) {  Product(0 .. j .. i-1 )[Pk[j]) * Product(i .. j .. m) [1 - Pk[j]] } ] - die Wahrscheinlichkeit, mindestens eine Variante zu erhalten, die den Filteranforderungen entspricht
  • Pk [i] - die Wahrscheinlichkeit, eine Variante zu erhalten, die die Anforderungen der Filter im zweiten Reiter erfüllt
  • n - Aufteilung des Optimierungsintervalls (Wert der Intervallpunkte in der 2. Registerkarte)

Die Optimierung wird genau so durchgeführt wie in den Optimierern für MetaTrader 4 und MetaTrader 5, jedoch wird nur ein Parameter optimiert, der ein Kauf- oder Verkaufssignal ist. Der Optimierungsschritt wird automatisch berechnet, basierend darauf, in wie viele Teile wir das Optimierungsintervall (Intervallpunkte) unterteilen. Der höchste Wert der zu optimierenden Zahl wird während des Suchprozesses im ersten Tab berechnet. Nachdem der Prozess im ersten Register abgeschlossen ist, kennen wir die Schwankungsbreite der Werte der optimierten Zahl. In der zweiten Registerkarte müssen wir also nur noch die Gittergenauigkeit einstellen, um dieses Intervall aufzuteilen. Die Variante nimmt einen Platz in der zweiten Registerkarte ein, der immer dann aktualisiert wird, wenn eine bessere Qualität erreicht wird. 

Wiederum wird die Wahrscheinlichkeit, eine Variante mit Qualitätsanforderungen zu erhalten, eine Verteilungsfunktion ähnlich der obigen haben. Das bedeutet, dass wir die gleichen Formeln mit einer leichten Anpassung verwenden können:

  • Integral(q0,qMax) [P(q)] = Integral(-qMax,-q0) [P(q)] =  K*Px(|q|) = Pz(|q|)  - das ist die Wahrscheinlichkeit, dass eine Variante mit |q| zwischen q0 und qMax während der aktuellen Iteration gefunden wird.
  • K = Py

Der einzige Unterschied ist hier der Koeffizient "K", der gleich der zuvor erhaltenen neuen Wahrscheinlichkeit ist. Die Wahrscheinlichkeit, eine Variante mit der geforderten Qualität zu erhalten, ist sehr gering, aber wir hatten in der ersten Registerkarte eine Menge solcher Varianten, also je mehr Varianten wir erhalten, desto besser. Außerdem, je mehr Varianten in der ersten Registerkarte produziert werden, desto bessere Varianten können in der zweiten Registerkarte erhalten werden. Die Berechnung ist ähnlich. Leider ist die Bernoulli-Formel hier nicht anwendbar, aber stattdessen kann die zuvor betrachtete Konstruktion verwendet werden. In diesem Fall wird die Optimierung einer Variante als eine separate Iteration interpretiert. Die Gesamtzahl der Iterationen wird also gleich der Anzahl der Iterationen sein. Wir brauchen mindestens eine Variante, die unseren Anforderungen entspricht, für die die vorherige Formel perfekt ist. Hier wird Pk durch Pz ersetzt, das durch die Familie der Pz[j](|q|) Funktionen bestimmt wird, da es für jede Optimierungsvariante eine eigene solche Funktion gibt.  

  • Pb = Sum(1...m...n)[ Sum(0... i ... C(n,m)-1) {  Product(0.. j .. i-1 )[Pz[j]) * Product(i.. j .. m) [1 - Pz[j]] } ]
  • n - die Anzahl der gefundenen Varianten im ersten Tab

Je länger Sie also Brute-Force anwenden, desto besser wird die Qualität. Vergessen Sie aber nicht, dass jeder Parameter die Wahrscheinlichkeiten und das Ergebnis beeinflusst. Verwenden Sie vernünftige Einstellungen, um einen hohen Ressourcenverbrauch zu vermeiden. Moderne Computer sind sehr leistungsfähig, aber vergessen Sie nicht, dass vernünftige Einstellungen und die Kenntnis der Prozessdetails die Berechnungseffizienz um ein Vielfaches erhöhen können.


Über das Festkleben an der Historie und die Überanpassung

Das Problem mit vielen automatisierten Handelssystemen ist, dass sie übertrainiert werden und sich zu stark an die Historie anpassen. Es ist möglich, ein System zu erstellen, das beeindruckende Ergebnisse zeigt, bis zu 1000 Prozent pro Monat. Aber solche Systeme funktionieren in der Realität nicht.

Je mehr Eingabeparameter ein Handelssystem hat und je größer die Variabilität der EA-Logik ist, desto stärker klebt ein solcher EA an der Historie. Die Sache ist die, dass wir einen sehr einfachen Prozess haben, wie ein Kurs in ein anderes Datenformat konvertiert wird. Es gibt immer Vorwärts- und Rückwärtskonvertierungsfunktionen, die sowohl den Vorwärts- als auch den Rückwärtskonvertierungsprozess von Daten durchführen können. Man kann es mit Verschlüsseln und Entschlüsseln vergleichen. Ein Beispiel für das Verschlüsseln ist das WinRar-Archiv. Im Zusammenhang mit unserer Aufgabe ist der Verschlüsselungsalgorithmus eine Kombination aus dem Optimierungsprozess und dem Vorhandensein der Handelslogik. Eine ausreichende Anzahl von Backtests im Optimierer und eine gewisse flexible Logik können Wunder bewirken. In diesem Fall dient die Handelslogik als Decoder, der zukünftige Preise auf Basis der Messwerte der Vergangenheit entschlüsselt.

Leider halten sich alle Expert Advisors bis zu einem gewissen Grad an die Vergangenheit. Es gibt aber auch einen logischen Teil, der eine gewisse Funktionalität in der Zukunft bewahren soll. Ein solcher Algorithmus ist extrem schwer zu erhalten. Wir kennen die maximalen Fähigkeiten der fairen Vorhersage eines bestimmten Algorithmus nicht und können daher keine Übertrainingsgrenzen bestimmen. Wir brauchen einen solchen Algorithmus, der die nächste Bewegung der Kerze mit der höchstmöglichen Wahrscheinlichkeit vorhersagen kann. Dabei gilt, je stärker der Grad der Preisdatenkompression, desto zuverlässiger ist der Algorithmus. Nehmen wir zum Beispiel die Funktion sin(w*t). Wir wissen, dass die Funktion einer unendlichen Anzahl von Punkten [X[i],Y[i]] entspricht — es ist ein Datenarray von unendlicher Länge, das zu einer kurzen Sinusfunktion komprimiert wird. Dies ist eine ideale Datenkompression. Eine solche Komprimierung ist in der Realität nicht möglich und wir haben immer eine Art von Datenkompressionskoeffizient. Je höher dieser Koeffizient ist, desto höher ist die Qualität der Definition der Marktformel.

Meine Methode verwendet eine feste Menge an variablen Daten. Trotzdem ist, wie bei jeder anderen Methode auch, eine Überanpassung möglich. Die einzige Möglichkeit, ein Übertraining mit der Historie zu vermeiden, besteht darin, das Kompressionsverhältnis zu erhöhen. Dies kann nur durch die Vergrößerung der analysierten Zeitspanne der Historie realisiert werden. Es gibt auch eine zweite Möglichkeit — die Anzahl der analysierten Balken in der Formel zu reduzieren (Bars To Equation). Es ist besser, die erste Methode zu verwenden, da durch die Reduzierung der Anzahl der Balken in der Formel die obere Grenze von "qMax" gesenkt wird, anstatt sie zu erhöhen. Zusammenfassend lässt sich sagen, dass es am besten ist, große Stichproben für das Training zu verwenden, genügend "Bars To Equation" zu verwenden, aber gleichzeitig muss daran gedacht werden, dass eine übermäßige Erhöhung dieses Wertes die Brute-Force-Geschwindigkeit reduziert und unweigerlich Risiken einer höheren Rate schafft, die über die Historie hinausgeht.


Empfehlungen zur Verwendung

Beim Testen habe ich einige wichtige Besonderheiten bei der Konfiguration des Hauptprogramms Awaiter.exe festgestellt. Hier sind die wichtigsten davon:

  1. Wenn Sie die gewünschten Einstellungen in allen Registerkarten vorgenommen haben, sollten Sie diese unbedingt speichern (Schaltfläche Save Settings)
  2. Spread Control kann in der zweiten Registerkarte aktiviert werden
  3. Beim Erzeugen von Kursen durch den HistoryWriter EA sollte eine möglichst große Stichprobe verwendet werden (mindestens 10 Jahre Historie)
  4. In der ersten Registerkarte können mehr Varianten gespeichert werden, 1000 scheint ausreichend ( Variantenspeicher )
  5. Stellen Sie keinen großen Wert für Intervallpunkte im Optimierungs-Tab ein (20-100 sollten ausreichen)
  6. Wenn Sie gute Einstellungen erhalten wollen, die einen Backtest auf realen Ticks bestehen können, dann benötigen Sie keine große Anzahl von Orders in Varianten (Min Orders)
  7. Sie sollten die Geschwindigkeit der Variantensuche kontrollieren (wenn Ihre Brute-Force schon lange läuft und keine Varianten gefunden werden, sollten Sie wahrscheinlich die Einstellungen ändern)
  8. Um stabile Ergebnisse zu erhalten, setzen Sie Deviation in den Bereich "0.1 - 0.2"; 0.1 ist die beste Option
  9. Wenn Sie die FOURIER-Gleichung in der Registerkarte der Optimierung verwenden, aktivieren Sie die Option "Spread Control" (die Formel ist extrem empfindlich gegenüber dem Rauschen des Spreads)


Schlussfolgerung

Bitte betrachten Sie diese Lösung nicht als heiligen Gral. Sie ist nur ein Hilfsmittel. Es ist schwierig, eine effiziente und nutzerfreundliche Lösung zu implementieren, die ohne zusätzliche Programmierung oder Optimierung in MetaTrader 4 und MetaTrader 5 Terminals verwendet werden kann. Diese Lösung ist genau das, und sie ist bereit für den allgemeinen Gebrauch. Ich hoffe,

Sie diese Methode nützlich finden. Natürlich gibt es noch viel Raum für Verbesserungen, aber im Allgemeinen ist es ein funktionierendes Werkzeug sowohl für die Marktforschung als auch für den Handel. Weitere Ergebnisse hängen von den Rechenkapazitäten ab, und nicht von Verbesserungen. Wie auch immer, ich denke, es wird in der Zukunft noch einige Verbesserungen geben.

Ich habe einige unrealisierte Ideen, die mehr Zeit benötigen. Eine davon ist die Konstruktion eines logischen Polynoms, das auf den bekanntesten Oszillator-Indikatoren und Preisindikatoren, wie den Bollinger Bändern oder dem Gleitenden Durchschnitt, basiert. Aber dieses Konzept ist ein bisschen komplexer. Ich würde gerne ein paar nützlichere Ideen implementieren, anstatt "am Indikatorschnittpunkt zu handeln". Ich hoffe auch, dass der Artikel etwas Neues und einige allgemeine nützliche Informationen für die Leser bietet.


Links zu den früheren Artikeln in dieser Serie:

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

Beigefügte Dateien |
Awaiter_Project.zip (5961.38 KB)
Andere Klassen in der Bibliothek DoEasy (Teil 67): Objektklasse der Charts Andere Klassen in der Bibliothek DoEasy (Teil 67): Objektklasse der Charts
In diesem Artikel werde ich die Objektklasse der Charts (das einzelne Chart eines Handelsinstruments) erstellen und die Kollektionsklasse von MQL5-Signalobjekten so verbessern, dass jedes in der Kollektion gespeicherte Signalobjekt alle seine Parameter beim Aktualisieren der Liste aktualisiert.
Andere Klassen in der Bibliothek DoEasy (Teil 66): MQL5.com die Kollektionsklasse der Signale Andere Klassen in der Bibliothek DoEasy (Teil 66): MQL5.com die Kollektionsklasse der Signale
In diesem Artikel werde ich die Kollektionsklasse der Signale des MQL5.com Signals-Dienstes mit den Funktionen zur Verwaltung von Signalen erstellen. Außerdem werde ich die Schnappschuss-Objektklasse der Markttiefe (Depth of Market, DOM) verbessern, um das gesamte Kauf- und Verkaufsvolumen im DOM anzuzeigen.
Neuronale Netzwerke leicht gemacht (Teil 13): Batch-Normalisierung Neuronale Netzwerke leicht gemacht (Teil 13): Batch-Normalisierung
Im vorigen Artikel haben wir begonnen, Methoden zur Verbesserung der Trainingsqualität neuronaler Netze zu besprechen. In diesem Artikel setzen wir dieses Thema fort und betrachten einen weiteren Ansatz — die Batch-Normalisierung.
Neuronale Netze leicht gemacht (Teil 12): Dropout Neuronale Netze leicht gemacht (Teil 12): Dropout
Als nächsten Schritt beim Studium von neuronalen Netzwerken schlage ich vor, die Methoden zur Erhöhung der Konvergenz beim Training von neuronalen Netzwerken zu besprechen. Es gibt mehrere solcher Methoden. In diesem Artikel werden wir uns einer von ihnen mit dem Namen Dropout zuwenden.