English Русский 中文 Español 日本語 Português
Muster mit Beispielel (Taiul I): Multiple-Tops

Muster mit Beispielel (Taiul I): Multiple-Tops

MetaTrader 5Handelssysteme | 17 August 2021, 13:59
792 0
Evgeniy Ilin
Evgeniy Ilin

Inhalt


Zusammenfassung

Muster werden im Internet häufig diskutiert, da sie von vielen Händlern verwendet werden. Muster können als visuelle Analysekriterien bezeichnet werden, um die Richtung der folgenden Preisbildung zu bestimmen. Der Algo-Handel unterscheidet sich hiervon. Es kann keine visuellen Kriterien für den algorithmischen Handel geben. Expert Advisors und Indikatoren haben individuelle Methoden, um mit den Preisreihen zu arbeiten. Auf beiden Seiten gibt es Vor- und Nachteile. Dem Code fehlt die Breite des menschlichen Denkens und die Qualität der menschlichen Analyse, aber der Code hat andere wertvolle Vorteile: unvergleichliche Geschwindigkeit und unvergleichliche Menge an numerischen oder logischen Daten, die pro Zeiteinheit verarbeitet werden. Es ist nicht einfach, der Maschine zu sagen, was sie tun soll. Dazu bedarf es einiger Übung. Mit der Zeit beginnt der Programmierer, die Maschine zu verstehen, und die Maschine beginnt, den Programmierer zu verstehen. Diese Artikelserie wird Anfängern dabei helfen, ihre Gedanken zu strukturieren und komplexe Aufgaben in einfachere Schritte zu unterteilen.


Über die Umkehrmuster

Für mich persönlich sind die Umkehrmuster zu vage definiert. Außerdem liegt ihnen keine Mathematik zugrunde. Um ehrlich zu sein, hat jedes Muster keine zugrundeliegende Mathematik und somit ist die einzige Mathematik, die hier in Betracht gezogen werden kann, die Statistik. Statistiken sind das einzige Kriterium für Wahrheit, aber Statistiken werden auf der Grundlage des realen Handels erstellt. Offensichtlich gibt es keine Quellen, die sehr präzise Statistiken liefern können. Es macht auch keinen Sinn, solche Daten für ein bestimmtes Forschungsproblem bereitzustellen. Die einzige Lösung ist hier das Backtesting und die Visualisierung im Strategie-Tester. Obwohl dieser Ansatz eine geringere Datenqualität bietet, hat er einen unbestreitbaren Vorteil: die Geschwindigkeit und die Datenmenge. 

Natürlich sind Umkehrmuster kein hinreichendes Instrument zur Bestimmung von Trendwenden, aber in Kombination mit anderen Analysemethoden, wie z.B. Levels oder Kerzen-Analyse, können sie das gewünschte Ergebnis liefern. Im Rahmen dieser Artikelserie werden die Muster nicht als besonders interessante Analysemethode betrachtet, aber sie können zum Üben von algorithmischen Handelsfertigkeiten verwendet werden. Zusätzlich zum Üben erhalten Sie ein interessantes und nützliches Hilfsmittel - wenn nicht für den Algo-Handel, dann für das Auge des Händlers. Nützliche Indikatoren haben einen hohen Stellenwert.


Warum Multiple-Tops — seine besonderen Merkmale

Dieses Muster ist im Internet aufgrund seiner Einfachheit recht populär geworden. Das Muster ist bei verschiedenen Handelsinstrumenten und in verschiedenen Chart-Zeitrahmen recht häufig anzutreffen, einfach weil es nichts Kompliziertes an sich hat. Wenn Sie sich das Muster genauer ansehen, können Sie außerdem feststellen, dass das Konzept der Methode durch die Verwendung von Algo-Trading und MQL5-Sprachfunktionen erweitert werden kann. Wir können versuchen, einen allgemeinen Code zu erstellen, der nicht nur durch einen Doppel Top begrenzt ist. Ein klug erstellter Prototyp kann zur Erkundung aller Mustervarianten und Nachfolger verwendet werden.

Der klassische Nachfolger des Multiple-Top ist die sehr beliebte Muster "Kopf-Schulter-Formation". Leider gibt es keine strukturierten Informationen darüber, wie dieses Muster gehandelt werden kann. Dieses Problem tritt bei vielen populären Strategien auf — denn es gibt viele schöne Worte, aber keine Statistiken. Ich werde in diesem Artikel versuchen zu verstehen, ob es möglich ist, sie im Rahmen des algorithmischen Handels zu verwenden. Die einzige Methode, Statistiken zu sammeln, ohne auf einem Demo- oder Realkonto zu handeln, ist die Nutzung der Möglichkeiten des Strategietesters. Ohne dieses Tool können Sie keine komplexen Schlussfolgerungen in Bezug auf eine bestimmte Strategie ziehen.


Kann das Konzept Doppel-Top erweitert werden?

In Bezug auf das Thema des Artikels werde ich versuchen, ein Diagramm in Form eines Baums von Mustern zu zeichnen, der von einer Doppelspitze ausgeht. Dies wird helfen zu verstehen, wie breit die Möglichkeiten dieses Konzepts sind:

Baum

Ich habe mich entschlossen, das Konzept der verschiedenen Muster mit der Annahme zu kombinieren, dass sie auf ungefähr derselben Idee beruhen. Diese Idee hat einen einfachen Anfang — finde eine gute Bewegung in eine beliebige Richtung und bestimme korrekt den Ort, an dem sie umkehren soll. Nach dem visuellen Kontakt mit dem vorgeschlagenen Muster sollte der Händler korrekt einige Hilfslinien einzeichnen, die bei der Bewertung, ob das Muster bestimmte Kriterien erfüllt, sowie bei der Bestimmung des Markteintrittspunkts zusammen mit dem Ziel- und Stop-Loss-Niveau helfen sollen. Anstelle des Ziels kann hier auch der Take-Profit verwendet werden.

Muster können einige gemeinsame Konstruktionsprinzipien haben, auf deren Grundlage das Konzept dieser Muster kombiniert werden kann. Eine solche klare Definition ist es, was algorithmische Händler von manuellen Händlern unterscheidet. Unsicherheit und Mehrfachauslegung derselben Prinzipien können zu enttäuschenden Folgen führen.

Die grundlegenden Muster sind wie folgt:

  1. Doppeltes Top
  2. Tripel-Top
  3. Kopf und Schultern

Diese Muster haben ähnliche Strukturen und Anwendungsprinzipien. Sie alle zielen darauf ab, Umkehrungen zu erkennen. Alle drei Muster haben eine ähnliche Logik hinsichtlich der Hilfslinien. Betrachten Sie bitte ein Beispiel für das Doppel-Top:

Doppeltes Extremum

In der obigen Abbildung sind alle erforderlichen Linien nummeriert und bedeuten Folgendes:

  1. Trendwiderstand
  2. Hilfslinie zur Definition einer pessimistischen Spitze (manche halten sie für eine Nackenlinie)
  3. Nackenlinie
  4. Optimistisches Ziel (es ist auch ein Take-Profit-Level für den Handel)
  5. Maximal zulässiges Stop-Loss-Niveau (es wird ganz oben angesetzt)
  6. Optimistische Prognoselinie (entspricht der vorherigen Trendbewegung)

Ein pessimistisches Ziel wird in Bezug auf den Schnittpunkt der Nackenlinie von der Kante aus bestimmt, die dem Markt am nächsten ist — wir nehmen den Abstand zwischen "1" und "2", der als "t" angegeben wird, und messen denselben Abstand in Richtung der vorgeschlagenen Umkehr. Das Minimum des optimistischen Ziels wird in ähnlicher Weise bestimmt, aber der Abstand wird zwischen "5" und "3" gemessen, was als "s" angegeben wird.


Schreiben des Code zur Darstellung von Multiple-Top

Beginnen wir mit der Definition der Argumentationslogik zur Bestimmung dieser Muster. Um ein Muster zu finden, sollten wir uns an die Logik Balken-für-Balken halten, d.h. wir werden nicht mit Ticks, sondern mit Balken arbeiten. In diesem Fall wird die Belastung des Terminals erheblich reduziert, da unnötige Berechnungen vermieden werden. Zunächst legen wir eine Klasse fest, die einen unabhängigen Beobachter symbolisiert, der nach dem Muster suchen wird. Alle Operationen, die für eine korrekte Mustererkennung erforderlich sind, werden Teil der Instanz sein, so dass die Suche innerhalb der Klasse durchgeführt wird. Ich habe mich für diese Lösung entschieden, um weitere Code-Änderungen zu ermöglichen, z. B. wenn wir die Funktionalität erweitern oder bestehende Funktionen ändern müssen.

Die Klasse zum Erkennen

Beginnen wir mit der Betrachtung des Klasseninhalts:

class ExtremumsPatternFamilySearcher// class simulating an independent pattern search
   {
   private:
   int BarsM;// how many bars on chart to use
   int MinimumSeriesBarsM;// the minimum number of bars in a row to detect a top
   int TopsM;// number of tops in the pattern
   int PointsPessimistM;// minimum distance in points to the nearest target
   double RelativeUnstabilityM;// maximum excess of the head size relative to the minimum shoulder
   double RelativeUnstabilityMinM;// minimum excess of the head size relative to the minimum shoulder
   double RelativeUnstabilityTimeM;// maximum excess of head and shoulders sizes
   bool bAbsolutelyHeadM;// whether a pronounced head is required
   bool bRandomExtremumsM;// random selection of extrema
     


   struct Top// top data
      {
      datetime Datetime0;// time of the candlestick closest to the market
      datetime Datetime1;// time of the next candlestick
      int Index0;// index of the candlestick closest to the market
      int Index1;// index of the next candlestick
      datetime DatetimeExtremum;// time of the top
      int IndexExtremum;// index of the top
      double Price;// price of the top
      bool bActive;// if the top is active (if not, then it does not exist)
      };
   
   struct Line// line
      {
      double Price0;// price of the candlestick closest to the market, to which the line is bound
      datetime Time0;// time of the candlestick closest to the market, to which the line is bound
      double Price1;// price of the farthest candlestick to which the line is bound
      datetime Time1;// time of the farthest candlestick to which the line is bound
      datetime TimeX;// time of the X point
      int Index1;// index of the left edge
      bool DirectionOfFormation;// direction
      double C;// free coefficient in the equation
      double K;// aspect ratio
   
      void CalculateKC()// find unknowns in the equation
         {
         if ( Time0 != Time1 ) K=double(Price0-Price1)/double(Time0-Time1);
         else K=0.0;
         C=double(Price1)-K*double(Time1);
         }
      
      double Price(datetime T)// function of line depending on time
         {
         return K*T+C;
         }
      };
   
   public:   
   
   ExtremumsPatternFamilySearcher(int BarsI,int MinimumSeriesBarsI,int TopsI,int PointsPessimistI, double RelativeUnstabilityI,
   double RelativeUnstabilityMinI,double RelativeUnstabilityTimeI,bool bAbsolutelyHeadI,bool bRandomExtremumsI)// parametric constructor
      {
      BarsM=BarsI;
      MinimumSeriesBarsM=MinimumSeriesBarsI;
      TopsM=TopsI;
      PointsPessimistM=PointsPessimistI;
      RelativeUnstabilityM=RelativeUnstabilityI;
      RelativeUnstabilityMinM=RelativeUnstabilityMinI;
      RelativeUnstabilityTimeM=RelativeUnstabilityTimeI;
      bAbsolutelyHeadM=bAbsolutelyHeadI;
      bRandomExtremumsM=bRandomExtremumsI;
      bPatternFinded=bFindPattern();
      }
      
   int FormationDirection;// direction of the formation (multiple top or bottom, or none at all) ( -1,1,0 )      
   bool bPatternFinded;// if the pattern was found during formation
   Top TopsUp[];// required upper extrema
   Top TopsDown[];// required lower extrema
   Top TopsUpAll[];// all upper extrema
   Top TopsDownAll[];// all lower extrema
   int RandomIndexUp[];// array for the random selection of the tops index
   int RandomIndexDown[];// array for the random selection of the bottoms index
   Top StartTop;// where the formation starts (top farthest from the market)
   Top EndTop;// where the formation ends (top closest to the market)
   Line Neck;// neck
   Top FarestTop;// top farthest from the neck (will be used to determine the head or the formation size) or the same as the head
   Line OptimistLine;// line of optimistic forecast
   Line PessimistLine;// line of pessimistic forecast
   Line BorderLine;// line at the edge of the pattern
   Line ParallelLine;// line parallel to the trend resistance
   
      
   private:
   void SetTopsSize();// setting sizes for arrays with tops
   bool SearchFirstUps();// search for tops
   bool SearchFirstDowns();// search for bottoms
   void CalculateMaximum(Top &T,int Index0,int Index1);// calculate the maximum price between two bars
   void CalculateMinimum(Top &T,int Index0,int Index1);// calculate the minimum price between two bars
   bool PrepareExtremums();// prepare extrema
   bool IsExtremumsAbsolutely();// control the priority of tops
   void DirectionOfFormation();// determine the direction of the formation
   void FindNeckUp(Top &TStart,Top &TEnd);// find neck for the bullish pattern
   void FindNeckDown(Top &TStart,Top &TEnd);// find neck for the bearish pattern
   void SearchFarestTop();// find top farthest from the neck
   bool bBalancedExtremums();// initial balancing of extrema (so that they do not differ much)
   bool bBalancedExtremumsHead();// if a pattern has more than 2 tops, we can check for a pronounced head
   bool bBalancedExtremumsTime();// require that the extrema be not very far in time relative to the minimum distance
   bool bBalancedHead();// balance the head (in other words, require that it be neither the first nor the last one on the list of tops, if there are more than three of them)
   bool CorrectNeckUpLeft();// adjust the neck so as to find the intersection of price and neck (this creates prerequisites for the previous trend) 
   bool CorrectNeckDownLeft();// similarly for the bottom
   int CorrectNeckUpRight();// adjust the neck so as to find the intersection of price and neck on the right or at the current price position, which is the same (to determine the entry point)
   int CorrectNeckDownRight();// similarly for the bottom
   void SearchLineOptimist();// calculate the optimistic forecast line
   bool bWasTrend();// determine whether a trend preceded the pattern definition (in this case the optimistic target line is considered as the trend beginning)
   void SearchLineBorder();// determine trend resistance or support (usually a sloping line)
   void CalculateParallel();// determine a line parallel to support or resistance (crosses the neck at the pattern low or high)
   bool bCalculatePessimistic();// calculate the line of the pessimistic target
   bool bFindPattern();// perform all the above actions
   int iFindEnter();// find intersection with the neck
   public:
   void CleanAll();// clean up objects
   void DrawPoints();// draw points
   void DrawNeck();// draw the neck
   void DrawLineBorder();// line at the border
   void DrawParallel();// line parallel to the border
   void DrawOptimist();// line of optimistic forecast
   void DrawPessimist();// line of pessimistic forecast
   };

Eine Klasse repräsentiert aufeinanderfolgende Operationen, die ein Mensch ausführen würde, wenn er anstelle einer Maschine stünde. Auf jeden Fall kann das Erkennen einer beliebigen Formation in eine Reihe einfacher, aufeinander folgender Operationen aufgeteilt werden. In der Mathematik gibt es eine Regel: Wenn du nicht weißt, wie du eine Gleichung lösen sollst, vereinfache sie. Diese Regel gilt nicht nur für die Mathematik, sondern auch für jeden Algorithmus. Die Erkennungslogik ist zunächst nicht klar. Aber wenn man weiß, wo man mit der Erkennung beginnen muss, wird die Aufgabe viel einfacher. In diesem Fall suchen wir, um das gesamte Muster zu finden, entweder nach Ober- oder Untergrenzen oder sogar nach beiden.

Tops und Bottoms bestimmen

Ohne Tops (Hochs) und Bottoms (Tiefs) ist das gesamte Muster sinnlos, da das Vorhandensein von Tops und Bottoms eine notwendige Bedingung für das Muster ist, obwohl diese Bedingung allein nicht ausreicht. Es gibt verschiedene Möglichkeiten, Tops, also die Hochs, zu bestimmen. Die wichtigste Bedingung ist das Vorhandensein einer ausgeprägten Halbwelle, wobei die Halbwelle durch zwei ausgeprägte gegenläufige Bewegungen bestimmt wird, die in unserem Fall mehrere Balken hintereinander in einer Richtung sein sollten. Zu diesem Zweck müssen wir die Mindestanzahl von Balken in einer Richtung bestimmen, die das Vorhandensein einer Bewegung anzeigen. Dazu stellen wir eine Eingabevariable zur Verfügung. 

bool ExtremumsPatternFamilySearcher::SearchFirstUps()// find tops
   {
   int NumUp=0;// the number of found tops
   int NumDown=0;// the number of found bottoms
   bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found
   bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found
   bool bNextUp=true;// can we move on to searching for the next top
   bool bNextDown=true;// can we move on to searching for the next bottom
   
   for(int i=0;i<ArraySize(TopsUp);i++)// before search, set all necessary tops to an inactive state
      {
      TopsUp[i].bActive=false;
      }
   for(int i=0;i<ArraySize(TopsUpAll);i++)// before search, set all tops to an inactive state
      {
      if (!TopsUpAll[i].bActive) break;
      TopsUpAll[i].bActive=false;
      }
               
   
   for(int i=0;i<BarsM;i++)
      {
      if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top
         {
         if ( bNextUp )// if it is allowed to search for the next top
            {
            bDown=true;
            for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops
               {
               if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward
                  {
                  bDown=false;
                  break;
                  }
               }
            if ( bDown )
               {
               TopsUpAll[NumUp].Datetime0=Time[i+MinimumSeriesBarsM-1];
               TopsUpAll[NumUp].Index0=i+MinimumSeriesBarsM-1;
               bNextUp=false;
               }
            }        
         }

      if ( MinimumSeriesBarsM+i < BarsM && bDown )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found
         {
         bUp=true;                  
         for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction
            {
            if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward
               {
               bUp=false;
               break;
               }
            }
         if ( bUp )
            {
            TopsUpAll[NumUp].Datetime1=Time[i];
            TopsUpAll[NumUp].Index1=i;
            TopsUpAll[NumUp].bActive=true;
            bNextUp=false;
            }   
         } 
      // after that, register the found formation as a top, if it is a top
      if ( bDown && bUp )
         {
         CalculateMaximum(TopsUpAll[NumUp],TopsUpAll[NumUp].Index0,TopsUpAll[NumUp].Index1);// calculate extremum between two bars
         bNextUp=true;
         bDown=false;
         bUp=false;
         NumUp++;
         }
      }
   if ( NumUp >= TopsM ) return true;// if the required number of tops have been found
   else return false;
   }

Bottoms, also die Tiefs, werden in umgekehrter Weise bestimmt:

bool ExtremumsPatternFamilySearcher::SearchFirstDowns()// find bottoms
   {
   int NumUp=0;
   int NumDown=0;
   bool bDown=false;// an auxiliary boolean which shows if a segment of bearish candlesticks has been found
   bool bUp=false;// an auxiliary boolean which shows if a segment of bullish candlesticks has been found
   bool bNextUp=true;// can we move on to searching for the next top
   bool bNextDown=true;// can we move on to searching for the next bottom

   for(int i=0;i<ArraySize(TopsDown);i++)// before search, set all necessary bottoms to an inactive state
      {
      TopsDown[i].bActive=false;
      }
   for(int i=0;i<ArraySize(TopsDownAll);i++)// before search, set all bottoms to an inactive state
      {
      if (!TopsDownAll[i].bActive) break;
      TopsDownAll[i].bActive=false;
      }

   for(int i=0;i<BarsM;i++)
      {
      if ( i+MinimumSeriesBarsM-1 < BarsM )// if remaining bars are enough to determine the extremum and we can start searching for the next top
         {
         if ( bNextDown )// if it is allowed to search for the next bottom
            {
            bUp=true;               
            for(int j=i;j<i+MinimumSeriesBarsM;j++)// determine the first extrema for upper tops
               {
               if ( Open[j]-Close[j] > 0 )//if at least one of the selected candlesticks was downward
                  {
                  bUp=false;
                  break;
                  }
               }
            if ( bUp )
               {
               TopsDownAll[NumDown].Datetime0=Time[i+MinimumSeriesBarsM-1];
               TopsDownAll[NumDown].Index0=i+MinimumSeriesBarsM-1;
               bNextDown=false;
               }
            }        
         }

      if ( MinimumSeriesBarsM+i < BarsM && bUp )// if the remaining bars are enough to determine the second half of the extremum and the previous half has been found
         {   
         bDown=true;                              
         for(int j=i;j<MinimumSeriesBarsM+i;j++)//determine further candlesticks in the opposite direction
            {
            if ( Open[j]-Close[j] < 0 )// if at least one of the selected candlesticks was upward
               {
               bDown=false;
               break;
               }
            }
         if ( bDown )
            {
            TopsDownAll[NumDown].Datetime1=Time[i];
            TopsDownAll[NumDown].Index1=i;
            TopsDownAll[NumDown].bActive=true;
            bNextDown=false;              
            }
         } 
      // after that, register the found formation as a bottom, if it is a bottom
      if ( bDown && bUp )
         {
         CalculateMinimum(TopsDownAll[NumDown],TopsDownAll[NumDown].Index0,TopsDownAll[NumDown].Index1);// calculate extremum between two bars
         bNextDown=true;
         bDown=false;
         bUp=false;            
         NumDown++;
         }
      }
      
   if ( NumDown == TopsM ) return true;//if the required number of bottoms have been found
   else return false;
   }

In diesem Fall habe ich nicht die Logik der Fraktale verwendet. Stattdessen habe ich meine eigene Logik für die Bestimmung der Tops und der Bottoms entwickelt. Ich glaube nicht, dass sie besser oder schlechter ist als die der Fraktale, aber zumindest ist es nicht notwendig, externe Funktionen zu verwenden. Außerdem müssen keine unnötigen eingebauten Sprachfunktionen verwendet werden, die manchmal gar nicht nötig sind. Diese Funktionen mögen gut sein, aber in diesem Fall sind sie überflüssig. Die Funktion bestimmt alle Tops und Bottoms, mit denen wir in Zukunft arbeiten werden. Das folgende Bild zeigt eine visuelle Darstellung dessen, was in dieser Funktion geschieht:

Suche nach Tops & Bottoms

Zuerst wird nach der Bewegung 1 gesucht, dann nach der Bewegung 2 und schließlich nach der Bewegung 3, was die Bestimmung des oberen oder unteren Teils bedeutet. Die Logik für 3 ist in zwei separaten Funktionen implementiert, die wie folgt aussehen:

void ExtremumsPatternFamilySearcher::CalculateMaximum(Top &T,int Index0,int Index1)// if 2 intermediate points are found, find High between them
   {
   double MaxValue=High[Index0];
   datetime MaxTime=Time[Index0];
   int MaxIndex=Index0;
   for(int i=Index0;i<=Index1;i++)
      {
      if ( High[i] >  MaxValue )
         {
         MaxValue=High[i];
         MaxTime=Time[i];
         MaxIndex=i;
         }
      }
   T.DatetimeExtremum=MaxTime;
   T.IndexExtremum=MaxIndex;
   T.Price=MaxValue;
   }
   
void ExtremumsPatternFamilySearcher::CalculateMinimum(Top &T,int Index0,int Index1)//if 2 intermediate points are found, find Low between them
   {
   double MinValue=Low[Index0];
   datetime MinTime=Time[Index0];
   int MinIndex=Index0;
   for(int i=Index0;i<=Index1;i++)
      {
      if ( Low[i] <  MinValue ) 
         {
         MinValue=Low[i];
         MinTime=Time[i];
         MinIndex=i;
         }
      } 
   T.DatetimeExtremum=MinTime;
   T.IndexExtremum=MinIndex;
   T.Price=MinValue;      
   }

Dann legen wir all dies in einen vorbereiteten Container. Die Logik ist wie folgt: Alle Strukturen, die innerhalb der Klasse verwendet werden, erfordern ein schrittweises Hinzufügen von Daten. Nachdem alle Schritte und Stufen durchlaufen wurden, werden die erforderlichen Daten ausgegeben. Anhand dieser Daten kann das Muster im Diagramm grafisch dargestellt werden. Natürlich können die obere und untere Bestimmungslogik unterschiedlich sein. Ich möchte hier nur eine einfache Erkennungslogik für komplexe Dinge zeigen.

Auswählen der Tops für die Arbeit

Die Tops und Bottoms, die wir gefunden haben, sind nur Zwischenschritte. Nachdem wir sie gefunden haben, müssen wir die Tops auswählen, die wir für am besten geeignet halten, um als Schultern zu fungieren. Wir können dies nicht mit Sicherheit bestimmen, da der Code nicht über maschinelles Sehen verfügt (im Allgemeinen ist die Verwendung solch komplexer Techniken wahrscheinlich nicht leistungsfördernd). Für den Moment wählen wir die Tops aus, die dem Markt am nächsten sind:

bool ExtremumsPatternFamilySearcher::PrepareExtremums()// assign the tops with which we will work
   {
   int Quantity;// an auxiliary counter for random tops
   int PrevIndex;// an auxiliary index for maintaining the order of indexes (increment only)
   
   for(int i=0;i<TopsM;i++)// simply select the tops that are closest to the market
      {
      TopsUp[i]=TopsUpAll[i];
      TopsDown[i]=TopsDownAll[i];
      }
   return true;   
   }

Visuell auf dem Chart des Symbols entspricht die Logik der Variante im lila Rahmen. Ich werde einige weitere Varianten zur Auswahl zeichnen:

Tops & Bottoms auswählen

In diesem Fall ist die Auswahllogik sehr einfach. Die ausgewählten Varianten sind 0 und 1, weil sie dem Markt am nächsten sind. Hier gilt alles für ein Doppel-Top. Die gleiche Logik wird aber auch für ein Triple-Top oder ein größeres Multiple-Top verwendet, der einzige Unterschied liegt in der Anzahl der ausgewählten Tops.

Diese Funktion wird in Zukunft erweitert werden, um die Möglichkeit zu bieten, Tops nach dem Zufallsprinzip auszuwählen, wie in der obigen Abbildung blau dargestellt. Dies wird mehrere Instanzen von Mustersuchern simulieren. Dies ermöglicht ein effizienteres und häufigeres Auffinden aller Muster im automatischen Modus.

Bestimmen der Richtung des Musters

Nachdem wir die Höchst- und Tiefststände identifiziert haben, müssen wir die Richtung der Formation bestimmen, sofern eine solche Formation an einem bestimmten Punkt des Marktes existiert. In diesem Stadium würde ich der Richtung, deren Extremum dem Markt am nächsten liegt, eine höhere Priorität einräumen. Auf der Grundlage dieser Logik verwenden wir die Variante 0 aus der Abbildung, denn das dem Markt am nächsten liegende Extremum ist das untere, nicht das obere (vorausgesetzt, die Marktsituation ist genau dieselbe wie in der Abbildung). Dieser Teil des Codes ist einfach:

void ExtremumsPatternFamilySearcher::DirectionOfFormation()// determine whether it is a double top (1) or double bottom (-1) (only if all tops and bottoms are found - if not found, then 0)
   {
   if ( TopsDown[0].DatetimeExtremum > TopsUp[0].DatetimeExtremum && TopsDown[ArraySize(TopsDown)-1].bActive )
      {
      StartTop=TopsDown[ArraySize(TopsDown)-1];
      EndTop=TopsDown[0];    
      FormationDirection=-1;
      }
   else if ( TopsDown[0].DatetimeExtremum < TopsUp[0].DatetimeExtremum && TopsUp[ArraySize(TopsUp)-1].bActive )
      {
      StartTop=TopsUp[ArraySize(TopsUp)-1];
      EndTop=TopsUp[0]; 
      FormationDirection=1;  
      }
   else FormationDirection=0;   
   }

Weitere Aktionen erfordern eine klar festgelegte Richtung. Die Richtung entspricht dem Mustertyp:

  1. Multiple-Top
  2. Multiple-Bottom

Diese Regeln gelten auch für die Formation Kopf und Schulter und alle anderen Varianten. Die Klasse sollte für alle Muster dieser Familie gemeinsam sein — diese Allgemeinheit funktioniert bereits teilweise.

Filter zum Aussortieren falscher Muster:

Lassen Sie uns nun weiter gehen. Da wir wissen, dass wir eine Richtung und eine der Möglichkeiten zur Auswahl von Tops und Bottoms haben, müssen wir für ein Multiple-Top Folgendes vorsehen: Die Tops, die zwischen den ausgewählten Tops liegen, sollten niedriger sein als der niedrigste der ausgewählten Tops. Bei einem mehrfachen Tiefpunkt sollten diese Tiefpunkte höher sein als der höchste der ausgewählten. Wenn in diesem Fall die Tops zufällig ausgewählt werden, sind alle ausgewählten Oberteile klar zu unterscheiden. Ansonsten ist diese Prüfung nicht erforderlich:

bool ExtremumsPatternFamilySearcher::IsExtremumsAbsolutely()// require the selected extrema to be the most extreme ones
   {
   if ( bRandomExtremumsM )// check only if we have a random selection of tops (in other case the check should be considered completed)
      {
      if ( FormationDirection == 1 )
         {
         int StartIndex=RandomIndexUp[0];
         int EndIndex=RandomIndexUp[ArraySize(RandomIndexUp)-1];
         for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones
            {
            for(int j=0;j<ArraySize(TopsUp);j++)
               {
               if ( TopsUpAll[i].Price >= TopsUp[j].Price )
                  {
                  for(int k=0;k<ArraySize(RandomIndexUp);k++)
                     {
                     if ( i != RandomIndexUp[k] ) return false;
                     }
                  }
               }
            }
         return true;
         }
      else if ( FormationDirection == -1 )
         {
         int StartIndex=RandomIndexDown[0];
         int EndIndex=RandomIndexDown[ArraySize(RandomIndexDown)-1];
         for(int i=StartIndex+1;i<EndIndex;i++)// check all tops between the selected ones
            {
            for(int j=0;j<ArraySize(TopsDown);j++)
               {
               if ( TopsDownAll[i].Price <= TopsDown[j].Price )
                  {
                  for(int k=0;k<ArraySize(RandomIndexDown);k++)
                     {
                     if ( i != RandomIndexDown[k] ) return false;
                     }
                  }
               }
            }
         return true;      
         }
      else return false;      
      }
   else
      {
      return true;
      }
   }

Wenn wir die richtige und die falsche Variante der zufälligen Top-Auswahl, die von der letzten Prädikatsfunktion durchgeführt wird, visuell darstellen, sieht es so aus:

Kontrolle der nicht berücksichtigten Tops


Diese Kriterien werden für die bullischen und bärischen Muster gespiegelt. Die Abbildung zeigt ein bullisches Muster als Beispiel. Der zweite Fall ist leicht vorstellbar.

Nachdem alle vorbereitenden Maßnahmen abgeschlossen sind, können wir uns auf die Suche nach der Nackenlinie machen. Verschiedene Händler zeichnen die Nackenlinie auf unterschiedliche Weise. Ich habe mehrere Arten der Konstruktion bedingt festgelegt:

  1. Visuell schief (nicht durch Schatten)
  2. Visuell waagerecht (nicht durch Schatten)
  3. Höchster oder tiefster Punkt, schief (durch Schatten)
  4. Höchster oder tiefster Punkt, waagerecht (durch Schatten)

Aus Sicherheitsgründen und um die Gewinnchancen zu erhöhen, bin ich der Meinung, dass die optimale Variante 4 ist. Ich habe diese aus folgenden Gründen gewählt:

  • Der Beginn einer Umkehrbewegung wird deutlicher erkannt
  • Dieser Ansatz ist einfacher im Code zu implementieren
  • Die Steigung wird eindeutig bestimmt (waagerecht)

Vielleicht ist das von der Konstruktion her nicht ganz korrekt, aber ich habe keine klaren Regeln gefunden. Aus der Sicht des Algo-Handels ist dies nicht kritisch. Wenn wir etwas Rationales in diesem Muster finden, wird uns der Tester oder die Visualisierung bestimmt etwas zeigen. Die weitere Aufgabe besteht in der Verstärkung der Handelsergebnisse, was jedoch eine ganz andere Aufgabe ist.

Ich habe zwei Spiegelfunktionen für die bullischen und bärischen Muster erstellt, die alle notwendigen Parameter des Halses definieren:

void ExtremumsPatternFamilySearcher::FindNeckUp(Top &TStart,Top &TEnd)// find the neck line based on the two extreme tops (for the classic multiple top)
   {
   double PriceMin=Low[TStart.IndexExtremum];
   datetime TimeMin=Time[TStart.IndexExtremum];
   for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point
      {
      if ( Low[i] < PriceMin )
         {
         PriceMin=Low[i];
         TimeMin=Time[i];
         }
      }
   // define the parameters of the anchor point and all parameters of the line equation
   Neck.Price0=PriceMin;
   Neck.TimeX=TimeMin;
   Neck.Time0=Time[0];
   Neck.Price1=PriceMin;
   Neck.Time1=TStart.DatetimeExtremum;
   Neck.DirectionOfFormation=true;
   Neck.CalculateKC();
   }
   
void ExtremumsPatternFamilySearcher::FindNeckDown(Top &TStart,Top &TEnd)// find the neck line based on two extreme bottoms (for the classic multiple bottom)
   {
   double PriceMax=High[TStart.IndexExtremum];
   datetime TimeMax=Time[TStart.IndexExtremum];
   for(int i=TStart.IndexExtremum;i>=TEnd.IndexExtremum;i--)// define the lowest point
      {
      if ( High[i] > PriceMax )
         {
         PriceMax=High[i];
         TimeMax=Time[i];         
         }
      }
   // define the parameters of the anchor point and all parameters of the line equation
   Neck.Price0=PriceMax;
   Neck.TimeX=TimeMax;
   Neck.Time0=Time[0];
   Neck.Price1=PriceMax;
   Neck.Time1=TStart.DatetimeExtremum;
   Neck.DirectionOfFormation=false;
   Neck.CalculateKC();
   }

Für ein korrektes und einfaches Zeichnen der Nackenlinie ist es besser, die gleichen Regeln für die Konstruktion der Nackenlinien für alle Muster der ausgewählten Familie zu verwenden. Auf der einen Seite werden so unnötige Details vermieden, die in unserem Fall nichts bringen. Um eine Nackenlinie für ein Multiple-Top von beliebiger Komplexität zu konstruieren, ist es besser, zwei extreme Spitzen des Musters zu verwenden. Die Indizes dieser Spitzen werden die Indizes sein, zwischen denen wir den niedrigsten oder höchsten Preis in dem ausgewählten Marktsegment suchen werden. Die Nackenlinie wird eine regelmäßige horizontale Linie sein. Die ersten Ankerpunkte sollten genau auf diesem Niveau liegen, während der Ankerzeitpunkt am besten genau mit dem Zeitpunkt der extremen Höchst- oder Tiefststände übereinstimmen sollte (je nachdem, welches Muster wir betrachten). Und so wird es auf dem Bild aussehen:

Nackenlinie

Das Fenster für die Suche nach dem Tief oder Hoch liegt genau zwischen dem ersten und dem letzten Hoch. Diese Regel gilt für jedes Muster dieser Familie, für eine beliebige Anzahl von Hochs und Tiefs.

Um das optimistische Ziel zu bestimmen, sollten wir zunächst die Mustergröße festlegen. Die Mustergröße ist der vertikale Abstand vom Kopf zur Nackenlinie in Punkten. Um den Abstand zu bestimmen, müssen wir zunächst das Oberteil finden, das am weitesten von der Nackenlinie entfernt ist. Dieses Top wird die Grenze des Musters sein:

void ExtremumsPatternFamilySearcher::SearchFarestTop()// define the farthest top
   {
   double MaxTranslation;// temporary variable to determine the highest top
   if ( FormationDirection == 1 )// if we deal with a multiple top
      {
      MaxTranslation=TopsUp[0].Price-Neck.Price0;// temporary variable to determine the highest top
      FarestTop=TopsUp[0];
      for(int i=1;i<ArraySize(TopsUp);i++)
         {
         if ( TopsUp[i].Price-Neck.Price0 > MaxTranslation ) 
            {
            MaxTranslation=TopsUp[i].Price-Neck.Price0;
            FarestTop=TopsUp[i];
            }
         }      
      }
   if ( FormationDirection == -1 )// if we deal with a multiple bottom
      {
      MaxTranslation=Neck.Price0-TopsDown[0].Price;// temporary variable to determine the lowest bottom
      FarestTop=TopsDown[0];      
      for(int i=1;i<ArraySize(TopsDown);i++)
         {
         if ( Neck.Price0-TopsDown[i].Price > MaxTranslation ) 
            {
            MaxTranslation=Neck.Price0-TopsDown[0].Price;
            FarestTop=TopsDown[i];
            }
         }      
      }
   }

Eine zusätzliche Prüfung ist erforderlich, um sicherzustellen, dass die Spitzenwerte nicht zu sehr voneinander abweichen. Nur wenn diese Prüfung erfolgreich ist, können wir mit den weiteren Schritten fortfahren. Genauer gesagt sollte es zwei Prüfungen geben: eine für die vertikale Größe der Extrema, die andere für die horizontale (Zeit). Wenn die Spitzen zeitlich zu weit entfernt sind, ist eine solche Variante ebenfalls nicht geeignet. Hier ist eine Prüfung für die vertikale Größe:

bool ExtremumsPatternFamilySearcher::bBalancedExtremums()// balance the tops
   {
   double Lowest;// the lowest top for the multiple top
   double Highest;// the highest bottom for the multiple bottom
   double AbsMin;// distance from the neck to the nearest top
   if ( FormationDirection == 1 )// for the multiple top
      {
      Lowest=TopsUp[0].Price;
      for(int i=1;i<ArraySize(TopsUp);i++)// find the lowest top
         {
         if ( TopsUp[i].Price < Lowest ) Lowest=TopsUp[i].Price;
         }
      AbsMin=Lowest-Neck.Price0;// determine distance from the lowest top to the neck
      if ( AbsMin == 0.0 ) return false;
      if ( ((FarestTop.Price - Neck.Price0)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage
      }
   else if ( FormationDirection == -1 )// for the multiple bottom
      {
      Highest=TopsDown[0].Price;
      for(int i=1;i<ArraySize(TopsDown);i++)// find the highest top
         {
         if ( TopsDown[i].Price > Highest ) Highest=TopsDown[i].Price;
         }
      AbsMin=Neck.Price0-Highest;// determine distance from the highest top to the neck
      if ( AbsMin == 0.0 ) return false;
      if ( ((Neck.Price0-FarestTop.Price)-AbsMin)/AbsMin >= RelativeUnstabilityM ) return false;// if the head is too much bigger than the lowest leverage
      }
   else return false;
   return true;   
   }

Um die richtige vertikale Größe der Tops zu bestimmen, benötigen wir zwei Tops. Das Erste ist das am weitesten von der Nackenlinie entfernte, das Zweite das ihm am nächsten liegende. Weichen diese Größen stark voneinander ab, kann sich diese Formation als ungültig erweisen, und es ist besser, das Risiko nicht einzugehen und sie als ungültig zu kennzeichnen. Ähnlich wie beim vorhergehenden Prädikat kann all dies von einer geeigneten Grafik begleitet werden, die zeigt, was richtig und was falsch ist:

Kontrolle der vertikalen Größe

Sie sind visuell leicht zu bestimmen, aber der Code braucht eine quantitative Metrik. In diesem Fall ist es so einfach wie folgt:

  • K = (Max - Min)/Min
  • K <= RelativeUnstabilityM

Diese Metrik ist recht effizient, um eine große Anzahl falscher Muster herauszufiltern. Nun, selbst der ausgefeilteste Code kann nicht effizienter sein als unser Auge. Das Einzige, was wir tun können, ist, die Logik so realitätsnah wie möglich zu gestalten — aber hier müssen wir wissen, wo wir aufhören müssen.

Die Prüfung der Waagerechten wird ähnlich aussehen. Der einzige Unterschied besteht darin, dass wir Balkenindizes als Größen verwenden (Sie können auch die Zeit verwenden, es gibt keinen grundlegenden Unterschied):

bool ExtremumsPatternFamilySearcher::bBalancedExtremumsTime()// balance the sizes of shoulders and head along the horizontal axis
   {
   double Lowest;// minimum distance between the tops
   double Highest;// maximum distance between the tops
   if ( FormationDirection == 1 )// for the multiple top
      {
      Lowest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum;
      Highest=TopsUp[1].IndexExtremum-TopsUp[0].IndexExtremum;
      for(int i=1;i<ArraySize(TopsUp)-1;i++)// find the lowest top
         {
         if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum < Lowest ) Lowest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum;
         if ( TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum > Highest ) Highest=TopsUp[i+1].IndexExtremum-TopsUp[i].IndexExtremum;
         }
      if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much
      }
   else if ( FormationDirection == -1 )// for the multiple bottom
      {   
      Lowest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum;
      Highest=TopsDown[1].IndexExtremum-TopsDown[0].IndexExtremum;
      for(int i=1;i<ArraySize(TopsDown)-1;i++)// find the lowest top
         {
         if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum < Lowest ) Lowest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum;
         if ( TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum > Highest ) Highest=TopsDown[i+1].IndexExtremum-TopsDown[i].IndexExtremum;
         }
      if ( double(Highest-Lowest)/double(Lowest) > RelativeUnstabilityTimeM ) return false;// if the width of one of the waves differs much 
      }
   else return false;
   return true;
   }

Für diese Prüfung können wir eine ähnliche Metrik verwenden. Visuell kann sie wie folgt ausgedrückt werden:

Kontrolle der horizontalen Abstände

In diesem Fall sind die quantitativen Kriterien die gleichen. Diesmal verwenden wir jedoch Indizes oder Zeiten anstelle von Punkten. Es wäre vielleicht besser, die Zahl, mit der wir vergleichen, separat zu implementieren, was die Möglichkeit für flexible Anpassungen bieten würde:

  • K = (Max - Min)/Min
  • K <= RelativeUnstabilityTimeM

Die Nackenlinie muss den Kurs auf der linken Seite kreuzen — dies bedeutet, dass dem Muster ein Trend vorausgegangen ist:

bool ExtremumsPatternFamilySearcher::CorrectNeckUpLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left
   {
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( Neck.DirectionOfFormation )// if the neck is found for a double top
      {
      for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( High[i] >= FarestTop.Price )// if the movement goes beyond the formation, then the formation is fake
            {
            return false;
            }         
         if ( Close[i] < Neck.Price0 && Open[i] < Neck.Price0 && High[i] < Neck.Price0 && Low[i] < Neck.Price0   )
            {
            Neck.Time1=Time[i];
            Neck.Index1=i;
            return true;
            }
         }
      }
   return false;
   }
   
bool ExtremumsPatternFamilySearcher::CorrectNeckDownLeft()// next the neck line must be corrected so that it finds an intersection with the price on the left
   {
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom
      {
      for(int i=StartTop.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( Low[i] <= FarestTop.Price )//  if the movement goes beyond the formation, then the formation is fake
            {
            return false;
            }         
         if ( Close[i] > Neck.Price0 && Open[i] > Neck.Price0 && High[i] > Neck.Price0 && Low[i] > Neck.Price0 )
            {
            Neck.Time1=Time[i];
            Neck.Index1=i;
            return true;
            }
         }
      }
   return false;
   }

Auch hier gibt es zwei Spiegelfunktionen für die bullischen und bärischen Muster. Nachstehend finden Sie eine grafische Darstellung dieses und des nächsten Prädikats:

Linke und rechte Abschnittskontrolle

Die blauen Rechtecke markieren die Marktsegmente, in denen wir die Abstände kontrollieren. Beide Segmente befinden sich hinter dem Muster, links und rechts von den extremen Spitzenwerten. 

Es gibt nur noch zwei Kontrollen:

  1. Wir brauchen ein Muster, das die Nackenlinie zum aktuellen Zeitpunkt (bei der Kerze Null) kreuzt.
  2. Dem Muster muss eine Bewegung vorausgegangen sein, die größer oder gleich dem Muster selbst ist.

Der erste Punkt ist für den algorithmischen Handel erforderlich. Ich glaube nicht, dass es sich lohnt, Formationen zu erkennen, nur um sie zu betrachten, obwohl auch diese Funktion zur Verfügung steht. Wir brauchen sowohl die Erkennung als auch das Auffinden des exakten Punktes, von dem aus wir handeln können — an dem wir sofort eine Position eröffnen können, weil wir wissen, dass wir am Einstiegspunkt sind. Der zweite Punkt ist eine der notwendigen Bedingungen, denn das Muster selbst ist ohne eine gute vorangehende Bewegung nutzlos.

Das Null-Kerzen-Kreuz (Überprüfung des Schnittpunkts auf der rechten Seite) wird wie folgt bestimmt:

int ExtremumsPatternFamilySearcher::CorrectNeckUpRight()// next the neck line must be corrected so that it finds an intersection with the price on the right
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( Neck.DirectionOfFormation )// if the neck is found for a double top
      {
      for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point
         {
         if ( High[i] > FarestTop.Price || Low[i] < Neck.Price0 )// if the movement goes beyond the formation, then the formation is fake
            {
            return -1;
            }         
         }
      }
      
   if ( Close[0] <= Neck.Price0 )
      {
      Neck.Time0=Time[0];
      return 1;
      }      
   return 0;
   }

int ExtremumsPatternFamilySearcher::CorrectNeckDownRight()// next the neck line must be corrected so that it finds an intersection with the price on the right
   {
   bool bCrossNeck=false;// indicates if the neck was crossed
   if ( !Neck.DirectionOfFormation )// if the neck is found for a double bottom
      {
      for(int i=EndTop.IndexExtremum;i>1;i--)// define the intersection point
         {
         if ( Low[i] < FarestTop.Price || High[i] > Neck.Price0  )// if the movement goes beyond the formation, then the formation is fake
            {
            return -1;
            }         
         }
      }
      
   if ( Close[0] >= Neck.Price0 )
      {
      Neck.Time0=Time[0];
      return 1;
      }   
      
   return 0;
   }

""

Auch hier haben wir zwei Spiegelfunktionen. Bitte beachten Sie, dass der Schnittpunkt auf der rechten Seite nicht als gültig angesehen wird, wenn sich der Kurs über das Muster hinaus bewegt hat und dann wieder zurückkehrt — dieses Verhalten wird hier behandelt und ist in der vorherigen Abbildung dargestellt.

Lassen Sie uns nun festlegen, wie wir den vorhergehenden Trend finden können. Bisher habe ich zu diesem Zweck die optimistische Prognoselinie verwendet. Wenn es ein Marktsegment zwischen der Nackenlinie und der Linie der optimistischen Prognose gibt, dann ist dies die gewünschte Bewegung. Diese Bewegung darf zeitlich nicht zu ausgedehnt sein, sonst handelt es sich offensichtlich nicht um eine Bewegung:

bool ExtremumsPatternFamilySearcher::bWasTrend()// did we find the movement preceding the formation (also move here the anchor point to the intersection)
   {
   bool bCrossOptimist=false;// denotes if the neck is crossed
   if ( FormationDirection == 1 )// if the optimistic forecast is at the double top
      {
      for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( High[i] > Neck.Price0 )// if the movement goes beyond the neck, then the formation is fake
            {
            return false;
            }         
         if ( Low[i] < OptimistLine.Price0 )
            {
            OptimistLine.Time1=Time[i];
            return true;
            }
         }
      }
   else if ( FormationDirection == -1 )// if the optimistic forecast is at the double bottom
      {
      for(int i=Neck.Index1;i<BarsM;i++)// define the intersection point
         {
         if ( Low[i] < Neck.Price0 )//  if the movement goes beyond the neck, then the formation is fake
            {
            return false;
            }         
         if ( High[i] > OptimistLine.Price0 )
            {
            OptimistLine.Time1=Time[i];
            return true;
            }
         }      
      }
   return false;
   }

Das letzte Prädikat kann auch grafisch wie folgt dargestellt werden:

Vorherige Bewegung

Lassen Sie uns die Überprüfung des Codes hier beenden und zu den visuellen Bewertungen übergehen. Ich denke, die wichtigsten Ideen der Methode wurden in diesem Artikel ausreichend beschrieben. Weitere Ideen werden im nächsten Artikel dieser Serie behandelt.

Schauen wir uns das Ergebnis im MetaTrader 5 Visual Tester an:

Ich verwende immer die Linienzeichnung im Chart, da sie schnell, einfach und übersichtlich ist. In der MQL5-Hilfe finden Sie Beispiele für die Verwendung beliebiger grafischer Objekte, einschließlich Linien. Ich werde den Code, der das zeichnet, hier nicht zur Verfügung stellen, aber Sie können das Ergebnis der Ausführung sehen. Natürlich könnte alles noch besser gemacht werden, aber noch ist es nur ein Prototyp. Daher glaube ich, dass wir hier das Prinzip der "Notwendigkeit und Hinlänglichkeit" anwenden können:

Tripel-Top im MetaTrader 5 Strategie-Tester im Visualizer

Hier ist ein Beispiel mit einem Tripel-Top. Dieses Beispiel schien mir interessanter zu sein. Doppelte Tops werden auf ähnliche Weise erkannt — Sie müssen nur die gewünschte Anzahl von Tops in den Parametern einstellen. Der Code findet solche Formationen nicht oft, aber das ist nur eine Demonstration. Der Code kann weiter verfeinert werden (was ich später vorhabe).


Weitere Entwicklungsideen

Später werden wir das berücksichtigen, was in diesem Artikel nicht gesagt wurde, und die Suchqualität für alle Formationen verbessern. Außerdem werden wir die Klasse so verfeinern, dass sie auch die Kopf-Schulter-Formationen erkennen kann. Wir werden auch versuchen, mögliche hybride Funktionen dieser Formationen zu finden; eine davon könnte "N Tops und mehrere Schultern" sein. Die Serie ist nicht nur dieser Familie von Mustern gewidmet und wird neues interessantes und nützliches Material enthalten. Es gibt verschiedene Ansätze für die Mustersuche, und die Idee dieser Serie ist es, so viele Muster wie möglich anhand verschiedener Beispiele zu zeigen und so verschiedene Möglichkeiten abzudecken, wie man eine komplexe Aufgabe in eine Reihe von einfacheren Aufgaben zerlegen kann. Die Serie wird Folgendes beinhalten:

  1. Andere interessante Muster
  2. Andere Methoden zur Erkennung verschiedener Formationstypen
  3. Handel mit historischen Daten und Sammlung von Statistiken für verschiedene Instrumente und Zeitrahmen
  4. Es gibt viele Muster, und ich kenne sie nicht alle (daher kann ich möglicherweise auch Ihr Muster berücksichtigen)
  5. Wir werden auch Niveaus berücksichtigen (da Niveaus oft verwendet werden, um Umkehrungen zu erkennen)


    Schlussfolgerung

    Ich habe versucht, das Material einfach und für jeden verständlich zu gestalten. Ich hoffe, jeder kann hier etwas Nützliches finden. Die Schlussfolgerung dieses speziellen Artikels ist, dass, wie man am visuellen Strategietester sehen kann, ein einfacher Code in der Lage ist, komplexe Formationen zu finden. Wir müssen also nicht unbedingt neuronale Netze verwenden oder komplexe Bildverarbeitungsalgorithmen schreiben/verwenden. Die Sprache MQL5 verfügt über reichhaltige Funktionen, um selbst die komplexesten Algorithmen zu implementieren. Die Möglichkeiten sind nur durch Ihre Phantasie und Ihren Fleiß begrenzt. 

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

    Beigefügte Dateien |
    Prototype.zip (309.42 KB)
    Besser Programmieren (Teil 03): Geben Sie diese 5 Dinge auf, um ein erfolgreicher MQL5-Programmierer zu werden Besser Programmieren (Teil 03): Geben Sie diese 5 Dinge auf, um ein erfolgreicher MQL5-Programmierer zu werden
    Dieser Artikel ist ein Muss für alle, die ihre Programmierkarriere verbessern wollen. Diese Artikelserie zielt darauf ab, Sie zum besten Programmierer zu machen, der Sie sein können, unabhängig davon, wie erfahren Sie sind. Die besprochenen Ideen eignen sich sowohl für MQL5-Programmierneulinge als auch für Profis.
    Besser Programmieren (Teil 02): Hören Sie auf, diese 5 Dinge zu tun, um ein erfolgreicher MQL5-Programmierer zu werden Besser Programmieren (Teil 02): Hören Sie auf, diese 5 Dinge zu tun, um ein erfolgreicher MQL5-Programmierer zu werden
    Dieser Artikel ist ein Muss für alle, die ihre Programmierkarriere verbessern wollen. Diese Artikelserie zielt darauf ab, Sie zum besten Programmierer zu machen, der Sie sein können, unabhängig davon, wie erfahren Sie sind. Die besprochenen Ideen eignen sich sowohl für MQL5-Programmierneulinge als auch für Profis.
    Besser Programmieren (Teil 04): Wie man ein schnellerer Entwickler wird Besser Programmieren (Teil 04): Wie man ein schnellerer Entwickler wird
    Jeder Entwickler möchte in der Lage sein, Code schneller zu schreiben, und die Fähigkeit, schneller und effektiver zu programmieren, ist keine besondere Fähigkeit, mit der nur wenige Menschen geboren werden. Es ist eine Fähigkeit, die von jedem Programmierer erlernt werden kann, unabhängig von seiner jahrelangen Erfahrung an der Tastatur.
    Grafik in der Bibliothek DoEasy (Teil 78): Animationsprinzipien in der Bibliothek. Schneiden von Bildern Grafik in der Bibliothek DoEasy (Teil 78): Animationsprinzipien in der Bibliothek. Schneiden von Bildern
    In diesem Artikel werde ich die Animationsprinzipien definieren, die in einigen Teilen der Bibliothek verwendet werden sollen. Außerdem werde ich eine Klasse entwickeln, mit der ein Teil des Bildes kopiert und an einer bestimmten Stelle des Formularobjekts eingefügt werden kann, wobei der Teil des Formularhintergrunds, über den das Bild gelegt werden soll, erhalten bleibt und wiederhergestellt wird.