Entwicklung von Bestandsindikatoren mit Volumensteuerung am Beispiel des Delta-Indikators

26 September 2018, 08:19
Alexey Kozitsyn
0
121

Inhalt

Einführung

Wie wir wissen, verfügt MetaTrader 5 über zwei Arten von Volumina:

  • Tick-Volumen, d.h. Anzahl der Ticks (Änderungen der Kursdaten), die während der Balkenbildung angekommen sind;
  • Real-Volumen, d.h. Anzahl der Transaktionen, die während der Balkenbildung stattgefunden haben.

Im Terminal wird das Real-Volumen einfach als Volumen angezeigt. Das interessiert uns. Da das Terminal neben der Tick-Historie auch Zeit und Umsatz bietet, ist es nun möglich, Aktienindikatoren zu entwickeln. Sie ermöglichen es uns zu sehen, was "hinter den Kulissen" geschieht, d.h. woraus das reale Volumen besteht: Volumen und Häufigkeit der ausgeführten Geschäfte sowie die Korrelation von Verkäufern und Käufern zu einem bestimmten Zeitraum. Das bedeutet, dass wir nun das Volumen in Komponenten aufteilen können. Diese Daten können die Genauigkeit unserer Handelsprognosen erheblich verbessern. Gleichzeitig ist es schwieriger, einen solchen Indikator zu entwickeln als einen herkömmlichen. Dieser Artikel beschreibt ausführlich die Reihenfolge und Feinheiten der Entwicklung von Aktienindikatoren, die Merkmale ihrer Arbeit und ihrer Tests. Als Beispiel werden wir den Delta-Indikator (Differenz) für Kauf- und Verkaufsvolumen entwickeln, die das reale Volumen bilden. Im Zuge der Entwicklung des Indikators sind auch die Regeln für die Arbeit mit dem Strom der Ticks zu beschreiben.

Wir sollten jedoch bedenken, dass das reale Volumen nur auf zentralisierten (Börsen-)Märkten verfügbar ist. Das bedeutet, dass es für Forex nicht verfügbar ist, da es sich um einen Over-the-Counter-Markt handelt. Wir werden die realen Volumina am Beispiel des Derivatemarktes (FORTS) der Moskauer Börse betrachten. Wenn Sie mit FORTS nicht vertraut sind, empfehle ich Ihnen dringend, den Artikel über Börsenpreise zu lesen.

Für wen ist dieser Artikel?

Fragen zu Tickdaten sind in letzter Zeit auf dem mql5.com Forum sehr häufig geworden. Diese Funktionalität ist relativ neu und wird ständig verbessert. Zunächst einmal ist dieser Artikel für Programmierer gedacht, die bereits wissen, wie man Indikatoren schreibt und bereit sind, ihre Fähigkeiten bei der Entwicklung von MetaTrader 5-Anwendungen zu verbessern. Der Artikel ist auch für Händler interessant, die die Börse beherrschen wollen und an der Analyse des Deltas und/oder ähnlicher Tick-Indikatoren interessiert sind.

1. Vorbereitung. Auswahl des Servers

Paradoxerweise sollte der Beginn der Indikatorenentwicklung mit einer Auswahl des Handelsservers beginnen. Eine notwendige Voraussetzung für das korrekte Funktionieren der Bestandsindikatoren: Der Server des Brokers sollten aktualisiert werden. Leider werden die Serverversionen des Brokers nicht übertragen, und es ist nicht immer möglich, sofort zu verstehen, ob die Daten korrekt sind.

Hoffentlich kann uns die Markttiefe bei diesem Thema helfen. Um sie zu öffnen, klicken Sie auf das Tabellensymbol neben dem Gerätenamen in der linken oberen Bildschirmecke (wenn es nicht angezeigt wird, überprüfen Sie, ob die Option "Schnellhandelstasten anzeigen" (F8) auf der Registerkarte Anzeigen aktiviert ist) oder drücken Sie Alt+B. Klicken Sie im Fenster "Markttiefe" auf die Schaltfläche "Zeit und Umsatz anzeigen". Stellen Sie außerdem sicher, dass der Minimalvolumenfilter nicht durch einen Rechtsklick auf die Tabelle gesetzt ist.

Wird der Server nicht aktualisiert, sendet er die sogenannten "ungewissen Richtungsgeschäfte" an die Markttiefe. Untersuchen wir das etwas genauer. Jede Transaktion hat einen Initiator: einen Käufer oder Verkäufer. Das bedeutet, dass die Transaktionseigenschaft (Kauf oder Verkauf) darin deutlich gekennzeichnet sein sollte. Wenn die Richtung des Geschäfts nicht bestimmt wird (in der Markttiefe als N/A markiert), wirkt sich dies auf die vom Indikator berechnete Delta (Differenz zwischen Kauf- und Verkaufsvolumen) Konstruktionsgenauigkeit aus. Nachfolgend sehen Sie aktualisierte und nicht aktualisierte Markttiefen (Abb. 1):

  


Fig. 1. Aktualisierte (links) und die alte (rechts) Markttiefe

Regel 1. Überprüfen, ob der Server aktualisiert wird.

Außerdem empfehle ich dringend, einen Server mit niedrigem Pingwert zu wählen. Je niedriger der Pingwert, desto schneller kann das Terminal Daten mit dem Server des Brokers austauschen. Wenn wir uns Abb. 1 genauer ansehen, handelt MetaTrader 5 Boradcasts mit einer Millisekunden-Genauigkeit, und je kleiner der Pingwert, desto schneller erhalten wir die Daten der Geschäfte. Überprüfen des Pings zum aktuellen Server (und wechseln Sie ggf. den Server) in der unteren rechten Ecke des Terminals:


Abb. 2. Die Latenzzeit zum gewählten Server beträgt 30.58 Millisekunden

Vergessen wir auch nicht, dass auch das Terminal auf die Version build 1881 oder höher aktualisiert werden sollte, das es alle bekannten Fehler bezüglich der Ticks eliminiert.

2. Methoden zum Erhalt der Tick-Historie. MqlTick Format

Angenommen wir haben einen Server gewählt, der uns die korrekte Tick-Historie liefert. Wie erhalten wir diese Historie? MQL5 bietet dafür zwei Funktionen:

  • CopyTicks() holt sich die Tick-Historie des benötigten Umfangs eines angegebenen Datums;
  • CopyTicksRange() holt sich die Tick-Historie einer bestimmten Zeitspanne.

Wir werden beide Funktionen für unseren Indikator benötigen. Mit ihnen erhalten wir die Ticks im Format von MqlTick. Diese Struktur speichert Daten über Zeit, Preise, Volumen sowie darüber, welche Daten mit dem neuen Tick genau geändert wurden. Wir können eine Tick-Historie von drei Typen erhalten. Dieser Typ wird durch das Flag definiert:

  • COPY_TICKS_INFO - Rückgabe von Ticks mit Bid- und/oder Ask-Preisänderungen;
  • COPY_TICKS_TRADE - gibt Ticks mit den letzten Preis- und Volumenänderungen zurück;
  • COPY_TICKS_ALL - gibt alle Ticks mit beliebiger Änderung zurück.

Wir benötigen einen Fluss der gehandelten Ticks (COPY_TICKS_TRADE) für unsere Zwecke. Weitere Informationen zu den Tick-Typen finden Sie in der Funktionsbeschreibung von CopyTicks.

Die MqlTick-Struktur ermöglicht es uns, die Werte der folgenden Felder zu analysieren:

  • volume - aktuelles Volumen des letzten Preises. Der Tick-Preis in unserem Indikator ist nutzlos, im Gegensatz zum Volumen, das auf diesem Tick gehandelt wird, der sehr wichtig ist und verwendet wird;
  • time_msc - letzte Aktualisierungszeit in Millisekunden. Mit diesem Parameter bestimmen wir die Kerze, zu der der Tick gehört, und erhalten die Zeit der nächsten Tick-Anfrage;
  • flags - Tick-Flags, geänderte Daten-IDs. Wir werden die Flags verwenden, um die Typen Kauf (TICK_FLAG_BUY) und Verkauf (TICK_FLAG_SELL) zu bestimmen.
Einfach ausgedrückt, der Indikator macht folgendes: Er erhält alle Trading-Ticks pro Kerze, verfolgt Kauf- und Verkaufsvolumen und zeigt deren Differenz (Delta) als Histogramm an. Wenn die Kerze mehr Käufer hat, ist der Balken des Histogramms blau. Andernfalls ist er rot. Alles ist ganz einfach!

3. Erster Start Berechnung der Historie

CTicks _ticks (Datei Ticks_article.mqh) wird als Hauptobjekt für die Arbeit mit Ticks im Indikator verwendet. Es wird verwendet, um alle Operationen mit Ticks auszuführen.

Die Indikatorberechnung wird in zwei Hauptblöcke unterteilt: Berechnung mit der Historie und in Echtzeit.

//+------------------------------------------------------------------+
//| Aufruf der nutzerdefinierten Indikatoren                         |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Beim ersten Start
   if(prev_calculated>0)                    // Wenn es nicht der erste Start ist
     {
      // Block 2
     }
   else                                     // Beim ersten Start
     {
      // Block 1
     }
//---
   return( rates_total );
  }

Beim ersten Start des Terminals oder beim Klicken auf die Schaltfläche Aktualisieren im Terminal (Block 1) sollten wir den Indikator für die Historie berechnen. Ursprünglich hatte ich geplant, eine universelle Berechnungsfunktion zu entwickeln, die sowohl für Historien- als auch für Echtzeitberechnungen verwendet werden sollte. Schließlich entschied ich mich jedoch, das Konzept zu ändern, um es einfacher zu machen und die Berechnungsgeschwindigkeit zu beschleunigen. Zuerst wird die Historie der geschlossenen Balken berechnet (CalculateHistoryBars()). Der aktuelle Balken (CalculateCurrentBar()) wird anschließend berechnet. Alle diese Aktionen werden im Folgenden beschrieben:

//--- 1. Initialisierung des Indikatorpuffers mit Anfangswerten
BuffersInitialize(EMPTY_VALUE);
//--- 2. Rücksetzen der Werte der Nachprüfparameter
_repeatedControl=false;
_controlNum=WRONG_VALUE;
//--- 3. Reset-Zeit des Balkens, in dem die Ticks gespeichert sind (Klick auf Aktualisieren).
_ticks.SetTime(0);
//--- 4. Setzt den Zeitpunkt, an dem der Download der Ticks der gebildeten Balken gestartet wird.
_ticks.SetFrom(inpHistoryDate);
//--- 5. Überprüfung, ob der Startzeitpunkt für den Downloads korrekt ist. 
if(_ticks.GetFrom()<=0)                 // Wenn keine Zeit gesetzt wurde
   return(0);                           // Verlassen
//--- 6. Setzen des Zeitpunkts, zu dem der Download der gebildeten Balkenhistorie beendet werden soll.
_ticks.SetTo( long( time[ rates_total-1 ]*MS_KOEF - 1 ) );
//--- 7. Download der Historie der geschlossenen Balken.
if(!_ticks.GetTicksRange())             // Nicht erfolgreich
   return(0);                           // Verlassen mit Fehler
//--- 8. Berechnung der Historie auf geschlossenen Balken.
CalculateHistoryBars( rates_total, time, volume );
//--- 9. Reset-Zeit des Balkens, in dem die Ticks gespeichert sind.
_ticks.SetTime(0);
//--- 10. Setzt den Zeitpunkt, an dem der Download der Ticks der gebildeten Balken gestartet wird.
_ticks.SetFrom( long( time[ rates_total-1 ]*MS_KOEF ) );
//--- 11. Setzen des Zeitpunkts, zu dem der Download der gebildeten Balkenhistorie beendet werden soll.
_ticks.SetTo( long( TimeCurrent()*MS_KOEF ) );
//--- 12. Download der Historie des aktuellen Balken.
if(!_ticks.GetTicksRange())             // Nicht erfolgreich
   return(0);                           // Verlassen mit Fehler
//--- 13. Zurücksetzen, sobald das Kopieren beendet ist.
_ticks.SetTo( ULONG_MAX );
//--- 14. Sichern der Zeit des letzten Ticks der erhaltenen Historie.
_ticks.SetFrom();
//--- 15. Aktuelle Balkenberechnung.
CalculateCurrentBar( true, rates_total, time, volume );
//--- 16. Setzen der Anzahl der Ticks für das anschließende Kopieren in Echtzeit.
_ticks.SetCount(4000);

Der Indikator verfügt über umfangreiche Kommentare, wir beschäftigen und daher nur mit den wichtigsten.

Punkt 3. "Reset-Zeit des Balkens, in dem die Ticks gespeichert sind". Das Objekt, das mit Ticks arbeitet, enthält die Öffnungszeit der Kerze, in der die entsprechenden Ticks gespeichert werden. Wenn man auf Aktualisieren klickt, wird der Indikator im Terminal von Anfang an neu berechnet. Damit die Ticks korrekt in der notwendigen Kerze gespeichert werden können, sollte ihre Zeit zurückgesetzt werden.

Punkt 4. "Setzt den Zeitpunkt, an dem der Download der Ticks der gebildeten Balken gestartet wird". Das Abrufen der Tick-Historie kann eine ziemlich zeitaufwändige Operation sein. Daher muss einem Nutzer die Möglichkeit geben, das Startdatum seines Downloads anzugeben. Der Parameter inpHistoryDate ist dafür vorgesehen. Bei einem Nullwert wird die Historie vom Anfang des aktuellen Tages heruntergeladen. In diesem Prototyp der Methode SetFrom(datetime) wird die Zeit in Sekunden angegeben. Wie bereits oben erwähnt, wird zunächst die Berechnung der gebildeten Balken des Indikators durchgeführt.

Punkt 5. "Überprüfung, ob der Startzeitpunkt des Downloads korrekt ist". Überprüfung des in Punkt 4. empfangenen Wertes.

Punkt 6. "Setzen des Zeitpunkts, zu dem der Download der gebildeten Balkenhistorie beendet werden soll". Der Zeitpunkt der Beendigung des Downloads der Historie der gebildeten Balken ist eine Millisekunde vor dem Öffnen der aktuellen Kerze (rates_total-1). In diesem Fall ist der Zeitpunkt der Beendigung des Downloads vom Typ 'long'. Wenn wir den Parameter der Methode übergeben, müssen wir explizit angeben, dass der Typ 'long' übergeben wird, falls die Klasse auch die Methode enthält, an die der Parameter mit dem Typ 'datetime' übergeben wird. Im Falle der Methode SetTo() der Klasse wird sie nicht mit dem Argument vom Typ 'datetime' überladen. Jedenfalls empfehle ich, den Parameter vom Typ 'long' explizit zu übergeben, um auf der sicheren Seite zu sein.

Punkt 7. "Download der Historie der geschlossenen Balken". Die Historie wird mit Hilfe der Funktion GetTicksRange() abgeholt, die eine Hülle für die Funktion CopyTicksRange() ist, mit zusätzlichen Prüfungen auf mögliche Fehler. Treten beim Download Fehler auf, wird beim nächsten Tick wiederholt der gesamte Historie abgefragt. Die unten angehängte Datei Ticks_article.mqh enthält weitere Details zu dieser Funktion sowie weitere Funktionen zum Arbeiten mit Ticks. 

Punkt 8. "Berechnung der Historie auf geschlossenen Balken". Die Berechnung an geformten Stangen wird im entsprechenden Artikelabschnitt ausführlich beschrieben.

Punkte 9-12. Berechnung der geschlossenen Balken. Nun ist es an der Zeit, die aktuelle Kerze zu berechnen. Der Kopierbereich ist eingestellt und die Ticks der aktuellen Kerze werden hier erhalten.

Punkt 13. "Zurücksetzen, sobald das Kopieren beendet ist". Wir werden weiterhin Ticks mit dem Objekt _ticks erhalten, obwohl wir das seit der Ankunft des letzten Ticks bis zum Ende des gesamten verfügbaren Verlaufs tun werden, und nicht von einem Moment zum anderen, wenn wir den Verlauf herunterladen. Daher ist es besser, den Zeitpunkt, an dem das Kopieren endet, zurückzusetzen - wir werden es bei der Berechnung in Echtzeit nicht mehr benötigen.

Punkt 14. "Sichern der Zeit des letzten Ticks der erhaltenen Historie". Wir benötigen die Zeit des letzten Ticks der Historie später als den Zeitpunkt des Beginns der Datenkopie in Echtzeit.

Punkt 15. "Aktuelle Balkenberechnung". Die Berechnung des aktuellen Balkens wird ebenfalls in einem separaten Teil des Artikels beschrieben und weist wichtige Unterschiede zur Methode der Berechnung der gebildeten Balken auf.

Punkt 16. "Setzen der Anzahl der Ticks für das anschließende Kopieren in Echtzeit". Zuvor erhielten wir Ticks mit der CopyTicksRange() Funktion, die eine Hülle zur Methode GetTicksRange() ist. In Echtzeit werden wir jedoch die Funktion CopyTicks() verwenden, die eine Hülle zur Methode GetTicks() ist. Die Methode SetCount() setzt die Anzahl der Ticks für nachfolgende Anfragen. Wir haben 4000 ausgewählt, weil das Terminal für jedes Symbol 4096 Ticks für den schnellen Zugriff speichert. Anfragen für diese Ticks werden mit der höchsten Geschwindigkeit ausgeführt. Das Einstellen des Wertes hat keinen Einfluss auf die Geschwindigkeit der Ticks (~1 ms), sei es 100 oder 4000.

Lassen Sie uns einen genaueren Blick auf die Berechnungsfunktionen werfen.

4. Die Funktion zur Berechnung der Historie der gebildeten Balken

Die Funktion selbst schaut wie folgt aus:

//+------------------------------------------------------------------+
//| Die Funktion zur Berechnung der Historie der gebildeten Balken   |
//+------------------------------------------------------------------+
bool CalculateHistoryBars(const int rates_total,    // Anzahl der berechneten Balken
                          const datetime& time[],   // Array der Eröffnungszeiten der Balken
                          const long& volume[]      // Array mit Werten des Real-Volumens
                          )
  {
//--- Total volumes
   long sumVolBuy=0;
   long sumVolSell=0;
//--- Barindex für den Eintrag in den Puffer
   int bNum=WRONG_VALUE;
//--- Abfrage der Anzahl der Ticks im Array
   const int limit=_ticks.GetSize();
//--- Schleife über alle Ticks
   for(int i=0; i<limit && !IsStopped(); i++)
     {
      //--- Definieren der Kerze für die Ticks
      if(_ticks.IsNewCandle(i))                         // Beginn der nächsten Bar
        {
         //--- Prüfen, ob der gebildete Kerzen des Index gespeichert ist.
         if(bNum>=0) // Wenn gesichert
           {
            //--- Prüfen, ob die Volumina gesichert wurden
            if(sumVolBuy>0 || sumVolSell>0) // Wenn alle Parameter gesichert
              {
               //--- Überwachen des Gesamtvolumens der Kerze
               VolumeControl(false,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
            //--- Eintragen der Werte in den Puffer
            DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
           }
         //--- Rücksetzen des Volumens der vorherigen Kerze
         sumVolBuy=0;
         sumVolSell=0;
         //--- Setzen des Kerzenindex gemäß der Eröffnungszeit
         bNum=_ticks.GetNumByTime(false);
         //--- Prüfen, ob der Index korrekt ist
         if(bNum>=rates_total || bNum<0) // Wen der Index falsch ist
           {
            //--- Verlassen ohne berechnete Historie
            return( false );
           }
        }
      //--- Eintragen des Volumens eines Ticks in die entsprechende Komponente
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);
     }
//--- Prüfen, ob die Volumina der zuletzt geschlossenen Kerze gesichert wurde
   if(sumVolBuy>0 || sumVolSell>0) // Wenn alle Parameter gesichert
     {
      //--- Verfolgen des Gesamtvolumens der Kerze
      VolumeControl(false,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
     }
//--- Eintragen der Werte in die Puffer
   DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
//--- Ende der Berechnung
   return( true );
  }

Die Idee hinter dieser Funktion ist es, erhaltene Ticks nach gebildeten Kerzen des aktuellen Zeitrahmens zu sortieren, die Differenz von Kauf- und Verkaufsvolumen jeder Kerze zu ermitteln und die erhaltenen Volumen- und Deltawerte in die Indikatorpuffer einzutragen.

Wie bereits erwähnt, war es ursprünglich geplant, eine gemeinsame Funktion für Historie und Echtzeitberechnungen zu schaffen. Mit dem Hinzufügen der Funktion zur Berechnung der Historie auf den gebildeten Balken verfolgen wir jedoch mehrere Ziele:

  • Vereinfachung des Algorithmus. Da die Balken bereits gebildet sind, werden ihnen keine neuen Ticks hinzugefügt. Dadurch können wir die im Folgenden beschriebenen übermäßigen Kontrollen, die für Echtzeitberechnungen notwendig sind, beseitigen;
  • Beschleunigung des Algorithmus. Zusätzlich zu der Tatsache, dass es keine zusätzlichen Prüfungen gibt, müssen wir auch nicht die Werte in den Puffern nach jedem Tick aktualisieren. Das Schreiben in die Puffer ist nur beim Umgang mit dem ersten Tick einer neuen Kerze durchzuführen;
  • Hinzufügen von Variabilität. Die Echtzeitberechnung wird nicht ständig benötigt. Wird der Indikator beispielsweise im EA durch gebildete Balken verwendet, genügt es, Berechnungen nur einmal pro Kerze allein mit dieser Funktion durchzuführen.

Die vollständige Beschreibung des Berechnungsalgorithmus und das Arbeiten mit der Tick-Historie finden Sie im Folgenden.

5. Funktion zur Berechnung der aktuellen Kerze

Achten Sie besonders auf die Funktion CalculateCurrentBar() im Indikatorcode.

//+------------------------------------------------------------------+
//| Berechnungsfunktion der aktuellen Kerze                          |
//+------------------------------------------------------------------+
void CalculateCurrentBar(const bool firstLaunch,   // Flag des Erststarts
                         const int rates_total,    // Anzahl der berechneten Flags
                         const datetime& time[],   // Array der Eröffnungszeiten der Bars
                         const long& volume[]      // Array mit Werten des Real-Volumens
                         )
  {
//--- Total volumes
   static long sumVolBuy=0;
   static long sumVolSell=0;
//--- Barindex für den Eintrag in den Puffer
   static int bNum=WRONG_VALUE;
//--- Prüfen der Flag des ersten Starts
   if(firstLaunch)                                 // Beim Erststart
     {
      //--- Rücksetzen der static Parameter
      sumVolBuy=0;
      sumVolSell=0;
      bNum=WRONG_VALUE;
     }
//--- Abfrage des Indes des vorletzter Ticks im Array
   const int limit=_ticks.GetSize()-1;
//--- 'limit' Tickzeit
   const ulong limitTime=_ticks.GetFrom();
//--- Schleife über alle Ticks (außer dem letzten)
   for(int i=0; i<limit && !IsStopped(); i++)
     {
      //--- 1. Vergleichen der Tick-Zeit mit der des letzten Ticks (Prüfung des Schleifenendes).
      if( _ticks.GetTickTimeMs( i ) == limitTime ) // Wenn Tickzeit gleich der des Limit-Ticks ist
         return;                                   // Verlassen
      //--- 2. Überprüfen, ob sich die nicht auf dem Chart vorhandene Kerze zu bilden beginnt.
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())                // Wenn die Kerze beginnt sich zu bilden
        {
         //--- Prüfen ob das Log gepflegt wird
         if(inpLog)
            Print(__FUNCTION__,": ATTENTION! Future tick ["+GetMsToStringTime(_ticks.GetTickTimeMs(i))+"]. Tick time "+TimeToString(_ticks.GetTickTime(i))+
                  ", time[ rates_total-1 ]+PerSec() = "+TimeToString(time[rates_total-1]+PeriodSeconds()));
         //--- 2.1. Setzen der (richtigen) Zeit der nächsten Tickabfrage
         _ticks.SetFrom(_ticks.GetTickTimeMs(i));
         //--- Verlassen
         return;
        }
      //--- 3. Definieren der Kerzen für die die Ticks bestimmt sind
      if(_ticks.IsNewCandle(i))                    // Wenn die nächste Kerze beginnt
        {
         //--- 3.1. Überprüfen, ob der gebildete Kerzenindex gespeichert ist.
         if(bNum>=0)                               // Wenn der Index gesichert wurde
           {
            //--- Prüfen, ob die Volumina gesichert wurden
            if(sumVolBuy>0 || sumVolSell>0)        // Wenn alle Parameter gesichert wurden
              {
               //--- 3.1.1. Bearbeitung des Gesamtvolumens der Kerze
               VolumeControl(true,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
           }
         //--- 3.2. Rücksetzen des Volumens der vorherigen Kerze
         sumVolBuy=0;
         sumVolSell=0;
         //--- 3.3. Merken des aktuellen Kerzenindex.
         bNum=rates_total-1;
        }
      //--- 4. Eintragen des Volumens eines Ticks in die entsprechende Komponente
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);
      //--- 5. Einzutragen der Werte in die Puffer
      DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);
     }
  }

Sie ist ähnlich wie die vorherige Funktion CalculateHistoryBars(), hat aber ihre eigenen Funktionen. Betrachten wir sie genauer. Der Funktionsprototyp ist unten aufgeführt:

//+------------------------------------------------------------------+
//| Berechnungsfunktion der aktuellen Kerze                          |
//+------------------------------------------------------------------+
void CalculateCurrentBar(const bool firstLaunch,   // Flag für den Erststart der Funktion
                         const int rates_total,    // Anzahl der berechneten Bars
                         const datetime& time[],   // Array der Eröffnungszeiten der Bars
                         const long& volume[]      // Array mit Werten des Real-Volumens
                         )

Beachten Sie, dass CalculateCurrentBar() in zwei Fällen verwendet werden sollte: beim Berechnen des aktuellen Kerzenverlaufs beim ersten Start und beim Durchführen von Berechnungen in Echtzeit. Das Flag firstLaunch ermöglicht die Auswahl des Berechnungsmodus. Der einzige Unterschied zwischen den Modi besteht darin, dass die statischen Variablen, die die Summen von Käufen und Verkäufen enthalten, sowie der Kerzenindex in den Puffern, die diese Summen enthalten, und ihre Differenz (Delta) beim ersten Start auf Null gesetzt werden. Ich möchte noch einmal betonen, dass in dem Indikator nur das Real-Volumen verwendet werden!

//--- Total volumes
   static long sumVolBuy=0;
   static long sumVolSell=0;
//--- Barindex für den Eintrag in den Puffer
   static int bNum=WRONG_VALUE;
//--- Prüfen der Flag des ersten Starts
   if(firstLaunch)                                 // Beim Erststart
     {
      //--- Rücksetzen der Volumensumme
      sumVolBuy=0;
      sumVolSell=0;
      //--- Rücksetzen des Kerzenindex
      bNum=WRONG_VALUE;
     }

Nach der Deklaration der statischen Variablen, wird der letzte Index des Arrays der Ticks abgefragt:

//--- Abfrage des Index des letzten Ticks
   const int limit=_ticks.GetSize()-1;
//--- 'limit' Tickzeit
   const ulong limitTime=_ticks.GetFrom();

Der Index dient als Trennzeichen für die Ticks-Iterationsschleife. Stellen wir die Bedingung auf, dass der letzte Tick und die Ticks mit der Zeit, die dem letzten Tick entspricht, nicht in die Berechnungen einbezogen werden. Warum? Trading-Ticks können in Bündeln ankommen, wenn eine Einzelmarktorder in mehreren Limit-Orders von verschiedenen Gegenparteien umgesetzt wird. Ein Bündel von Ticks (Deals) besteht aus Deals, die gleichzeitig (mit Millisekundengenauigkeit) und mit dem gleichen Typ (Kauf oder Verkauf) durchgeführt werden (Abb. 3). Beachten Sie, dass mehrere Tick-Bündel im Terminal innerhalb derselben Millisekunde als angekommen angezeigt werden können, da die Börsenübertragung nur Nanosekunden betragen kann. Um es selbst zu sehen, starten Sie das unten angehängte Skript test_getTicksRange.

Abb. 3. Tick-Bündel (Market Buy Order initiiert 4 Deals bestehend aus 26 Lots)

Um das Volumen richtig zu berücksichtigen, sollte ein Tick-Bündel nur einmal berechnet werden, wenn es vollständig an das Terminal übergeben wird, d.h. wenn das im Folgezeitpunkt abgeschlossene Geschäft verfügbar wird (Abb. 4).


Abb. 4. Durchführung des Geschäfts bei .373, Berechnung des Bündels bei .334

Wir können nicht sicher sein, ob das Bündel vollständig im Terminal angekommen ist, bis das Geschäft nach dem Paket verfügbar wird, da das Paket möglicherweise in Teilen ankommt. Darauf werde ich hier nicht näher eingehen, sondern beachten Sie es nur. So können wir die Regel 2 definieren:

Regel 2. Ein Tick-Bündel sollte nur nach dem Empfangen des Ticks nach diesem Bündel berechnet werden.

Wir haben die Zeit des zuletzt erhaltenen Ticks gespeichert in p. 13 des ersten Startalgorithmus. Nun, lassen Sie es uns nutzen, indem wir limitTime in die Variable schreiben.

Lassen Sie uns nun direkt zur Tick-Berechnungsschleife übergehen:

//--- 1. Vergleichen der Tick-Zeit mit der des letzten Ticks (Prüfung des Schleifenendes).
      if( _ticks.GetTickTimeMs( i ) == limitTime ) // Wenn Tickzeit gleich der des Limit-Ticks ist
         return;                                   // Verlassen

Punkt 1. "Vergleichen der Tick-Zeit mit der des letzten Ticks". Wie bereits erwähnt, wird der letzte Tick bei der Berechnung nicht berücksichtigt, da die Berechnung nur durch gebildete Tick-Bündel erfolgt. Aber wir wissen auch, dass das letzte Tick-Bündel teilweise kopiert werden kann. Das bedeutet, dass wir alle Ticks (falls vorhanden) des Bündels von der Berechnung ausschließen sollten.

//--- 2. Überprüfen, ob sich die nicht auf dem Chart vorhandene Kerze zu bilden beginnt.
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())

Punkt 2. "Überprüfen, ob sich die nicht auf dem Chart vorhandene Kerze zu bilden beginnt". Das klingt vielleicht etwas seltsam. Wie kann sich die nicht auf dem Chart vorhandene Kerze bilden? Um diese Frage zu beantworten, müssen Sie die Besonderheiten der Verarbeitung/des Empfangs von Tick-Daten im Terminal verstehen. Diese Besonderheiten wurden durch eine umfangreiche Kommunikation mit den Entwicklern über den Service Desk geklärt. Ich werde sie hier beschreiben: 

Terminal-Ticks werden in einem separaten Vorgang gesammelt, unabhängig von Indikator und EA-Leistung. Kerzen werden in einem anderen Vorgang erstellt - der Indikator Ausführung. Diese Ströme sind nicht miteinander synchronisiert. Nachdem ein Tick einer Kerze erscheint, wird der Indikator berechnet. Es wird kein einziger Tick übergangen. Das bedeutet, dass durch den Aufruf der CopyTicks() Funktion aktuellere Tick-Daten erhalten können, als die Daten der Kerze.

In der Praxis bedeutet dies Folgendes. Bei der Berechnung der Kerze rates_total-1 kann der Indikator die Ticks der nächsten Kerze erhalten, die noch unvollständig ist (ein Tick wurde noch nicht auf sie angewendet). Um diese Situation (sowie auch einen Array-Überlauf) zu vermeiden, müssen wir diesen Check hinzufügen.

Regel 3. Beachten Sie, dass Ticks, die noch nicht auf dem Kerzenchart erschienen sind, erhalten werden können.

Wird ein "zukünftiger" Tick (ohne die entsprechend geformte Kerze) erkannt, sollten wir die Zeit, zu der die nächste Tick-Anfrage erfolgen soll, neu schreiben (Punkt 2.1). Außerdem sollten wir die Schleife und die Funktion sofort verlassen, während wir auf einen neuen Tick und die Bildung einer neuen Kerze auf dem Chart warten:

//--- 2. Überprüfen, ob sich die nicht auf dem Chart vorhandene Kerze zu bilden beginnt.
      if(_ticks.GetTickTime(i)>=time[rates_total-1]+PeriodSeconds())                // Wenn die Kerze beginnt sich zu bilden
        {
         //--- 2.1. Setzen der (richtigen) Zeit der nächsten Tickabfrage
         _ticks.SetFrom(_ticks.GetTickTimeMs(i));
         //--- Verlassen
         return;
        }

Der folgende Algorithmus umfasst nahezu komplett die Funktion CalculateHistoryBars(). Betrachten wir das genauer.

//--- 3. Definieren der Kerzen für die die Ticks bestimmt sind
      if(_ticks.IsNewCandle(i))

Punkt 3. Definieren der Kerze, in der die Ticks gespeichert sind. Hier vergleichen wir die i-te Zeit des Ticks und die Öffnungszeit der Kerze, zu der die Ticks gehörten. Wenn die Zeit des i-ten Tick über die Kerzengrenzen hinausgeht, wird die Öffnungszeit der Kerze geändert und der Algorithmus zur Vorbereitung der folgenden Kerze zur Analyse ausgelöst:

//--- 3. Definieren der Kerzen für die die Ticks bestimmt sind
      if(_ticks.IsNewCandle(i))                    // Wenn die nächste Kerze beginnt
        {
         //--- 3.1. Überprüfen, ob der gebildete Kerzenindex gespeichert ist.
         if(bNum>=0)                               // Wenn der Index gesichert wurde
           {
            //--- Prüfen, ob die Volumina gesichert wurden
            if(sumVolBuy>0 || sumVolSell>0)        // Wenn alle Parameter gesichert wurden
              {
               //--- 3.1.1. Bearbeitung des Gesamtvolumens der Kerze
               VolumeControl(true,bNum,volume[bNum],time[bNum],sumVolBuy,sumVolSell);
              }
           }
         //--- 3.2. Rücksetzen des Volumens der vorherigen Kerze
         sumVolBuy=0;
         sumVolSell=0;
         //--- 3.3. Merken des aktuellen Kerzenindex.
         bNum=rates_total-1;
        }

Punkt 3.1. Überprüfen, ob der gebildete Kerzenindex gespeichert ist. Im Modus der Historienberechnung (erster Start) verhindert diese Prüfung den Zugriff auf die Zeit- und Volumenarrays unter dem falschen Index (-1). Als nächstes überprüfen wir, ob Geschäfte während der Kerze gemacht wurden. Da es keine Geschäfte gab, ist eine Volumenkontrolle nicht erforderlich.

Punkt 3.1.1. Vollständige Volumenkontrolle. Während des Funktion VolumeControl() werden die vom Indikator pro Kerze angesammelten Kauf- und Verkaufsvolumina summiert und mit dem "Referenzvolumen" verglichen, d.h. das reale Volumen, das direkt von einer Börse weitergegeben wird (um den Wert aus dem Volume[] Array der gebildeten Kerze). Wenn das Austauschvolumen mit dem kumulierten übereinstimmt, fahren wir mit weiteren Berechnungen fort. Aber was ist, wenn es das nicht tut? Man wird sich vielleicht fragen, wie es sein kann. Das Gesamtvolumen ist gleich. Der einzige Unterschied besteht darin, dass wir einen in unserem Indikator berechnet haben, während der andere von der Börse kam. Die Volumina sollten auf jeden Fall übereinstimmen! 

Nun, so sollte es sein. Das sollten sie. Diese Regel sollte auf alle Kerzen angewendet werden. Was genau unser Indikator bewirkt:

  • Der Indikator erhält alle Ticks;
  • Null-Tick-Zeit wird verwendet, um die berechnete Kerzenzeit zu definieren (z.B. ist auf М1 die Null-Tick-Zeit 10:00:00:00.123, d.h. die Öffnungszeit der Kerze ist 10:00, und das Delta wird dafür berechnet);
  • Überprüfen der Zeit von jedem Tick;
  • Addieren des Volumens von jedem Tick entweder zu Käufen oder Verkäufen;
  • Warten, bis der Tick die berechneten Kerzenränder verlässt (mit einer Zeit größer als 10:00:59.999), um das Delta der 10:00 Uhr Kerze anzuzeigen;
  • Die Zeit des Ticks, die über die berechnete Kerze hinausgeht (z.B. 10:01:00.46), wird verwendet, um eine neue Kerze (10:01) zu öffnen. Für diese Kerze wird das nächste Delta berechnet. So wird der gesamte Prozess wiederholt.

Bis jetzt scheint es ziemlich einfach zu sein. In Echtzeit (für die Kerze rates_total-1) sollten wir uns jedoch an die oben genannten "zukünftigen" Ticks (Regel 3) erinnern, die auftreten, wenn der Tick der neuen Kerze ankommt, während die aktuelle Chart-Kerze sich noch nicht geschlossen hat. Diese Funktion wirkt sich auch auf den Volumenkontrolle aus! Beim Umgang mit dem Ticks enthält der Indikator noch den veralteten Volumenwert im Array Volume[] (der Wert bleibt unverändert). Das bedeutet, dass wir das vom Indikator gesammelte Volumen nicht korrekt mit dem des Arrays Volume[] Eins vergleichen können. In der Praxis stimmt das Volumen[rates_total-1] des Referenzvolumens manchmal nicht mit der (sumVolBuy+sumVolSell) Volumensumme des Indikators überein. Die Funktion VolumeControl() bietet zwei Lösungen:

  1. Neuberechnung des Kerzenvolumens und Vergleich mit dem mit der Funktion CopyRealVolume() erhaltenen Referenzwert;
  2. Wenn die erste Option das Problem nicht löst, wird das Flag der Volumenkontrolle gesetzt, wenn eine neue Kerze gebildet wird.

So versucht die erste Methode, das Kontrollproblem vor der Entstehung der neuen Kerze zu lösen, während die zweite garantiert das Problem nach ihrer Entstehung löst.

Punkt 3.2. "Zurücksetzen der vorherigen Kerzenvolumina". Nachdem die neue Kerze gebildet wurde, setzen wir die Volumenzähler auf Null zurück.

Punkt 3.3. "Merken des aktuellen Kerzenindex". Ein weiterer Vorteil der Trennung von Berechnungsfunktionen in die Funktion zur Berechnung durch gebildete Balken und die zur aktuellen Kerzenberechnung. Der aktuelle Kerzenindex ist immer rates_total-1.

//--- 4. Eintragen des Volumens eines Ticks in die entsprechende Komponente
      AddVolToSum(_ticks.GetTick(i),sumVolBuy,sumVolSell);

Punkt 4. Addieren des Tick-Volumens zum Gesamtvolumen. Wir verwenden zunächst das Flag des analysierten Ticks, um herauszufinden, welche Daten sich geändert haben:

//+------------------------------------------------------------------+
//| Summieren des Tick-Volumens zum Gesamtvolumen                    |
//+------------------------------------------------------------------+
void AddVolToSum(const MqlTick &tick,        // Geprüfte Tick-Parameter
                 long& sumVolBuy,            // Gesamtvolumen Kauf (out)
                 long& sumVolSell            // Gesamtvolumen Verkauf (out)
                )
  {
//--- Prüfen der Richtung des Ticks
   if(( tick.flags&TICK_FLAG_BUY)==TICK_FLAG_BUY && ( tick.flags&TICK_FLAG_SELL)==TICK_FLAG_SELL) // Wenn der Tick für beide Richtungen ist
        Print(__FUNCTION__,": ERROR! Tick '"+GetMsToStringTime(tick.time_msc)+"' is of unknown direction!");
   else if(( tick.flags&TICK_FLAG_BUY)==TICK_FLAG_BUY)   // Im Falle eines Kauf-Ticks
        sumVolBuy+=(long)tick.volume;
   else if(( tick.flags&TICK_FLAG_SELL)==TICK_FLAG_SELL) // Im Falle eines Verkauf-Ticks
        sumVolSell+=(long)tick.volume;
   else                                                  // Wenn es kein Handelstick ist
        Print(__FUNCTION__,": ERROR! Tick '"+GetMsToStringTime(tick.time_msc)+"' is not a trading one!");
  }

Auch hier sollten wir uns auf die Regel 1 konzentrieren. Wenn die Arbeit auf einem Server stattfindet, der Transaktionen in eine unbekannte Richtung sendet, ist es unmöglich festzustellen, wer die Transaktion ausgelöst hat - der Käufer oder der Verkäufer. Daher wird das Journal die entsprechenden Fehler fortsetzen. Wird der Initiator bestimmt, wird das Volumen zum Gesamtvolumen der Käufe oder Verkäufe addiert. Wenn das Flag keine Daten über den Initiator einer Transaktion enthält, wird auch der Fehler empfangen.

//--- 5. Eintragen der Werte in die Puffer
      DisplayValues(bNum,sumVolBuy,sumVolSell,__LINE__);

Punkt 5. Eintragen der Werte in die Puffer. Die Funktion DisplayValues() verfolgt den Index der Indikatorpuffer (dazu übergeben wir den Index an die Funktion), die Deltaberechnung und das Schreiben des Deltas sowie die Kauf- und Verkaufsvolumen in die Puffer.

6. Echtzeitberechnung

Beschreiben wir den Block der Echtzeitberechnung

//--- 1. Überprüfen der neue Balkenformation
if(rates_total>prev_calculated) // Im Falle einer neuen Bar
  {
   //--- Initialisieren des Puffers der rates_total-1 Indices mit Null
   BuffersIndexInitialize(rates_total-1,EMPTY_VALUE);
   //--- 2. Überprüfen, ob das Volumen des Balken rates_total-2 verfolgt werden soll
   if(_repeatedControl && _controlNum==rates_total-2)
     {
      //--- 3. Erneutes Prüfen
      RepeatedControl(false,_controlNum,time[_controlNum]);
     }
   //--- 4. Rücksetzen der Werte des erneuten Prüfens
   _repeatedControl=false;
   _controlNum=WRONG_VALUE;
  }
//--- 5. Laden der neuen Ticks
if(!_ticks.GetTicks() )               // Nicht erfolgreich
   return( prev_calculated );         // Verlassen mit Fehler
//--- 6. Sichern der Zeit des letzten Ticks der erhaltenen Historie.
_ticks.SetFrom();
//--- 7. Echtzeitberechnung
CalculateCurrentBar(false,rates_total,time,volume);

Punkt 1. Überprüfen der neue Balkenformation. Diese Überprüfung ist sehr wichtig. Wie wir in 2.1.1.1 festgestellt haben, sollte die Prüfung, wenn sie nicht im Volumenkontrollverfahren (Echtzeitberechnung) der Hauptberechnungsfunktion bestanden wird, zum Zeitpunkt einer neuen Balkenbildung bestanden werden. Das ist genau der richtige Moment!

Punkt 2. Überprüfen, ob das Volumen des Balken rates_total-2 verfolgt werden soll. Wenn das wiederholte Steuerflag markiert ist und an der neu gebildeten Kerze rates_total-2 durchgeführt werden soll, führen Sie eine erneute Überprüfung durch (p. 3).

Punkt 3. Durchführen einer erneuten Überprüfung. Wie bereits erwähnt, werden bei der Nachprüfung alle Ticks pro Kerze empfangen. Außerdem definieren wir Kauf- und Verkaufsvolumina, berechnen das Delta und vergleichen die Summe der Volumina mit einem Referenzwert.

Punkt 5. Neue Ticks herunterladen. Abfragen der Ticks seit der Ankunft des letzten Ticks beim vorherigen Indikatorstart. Wenn wir in Echtzeit rechnen, erhalten wir Ticks mit der GetTicks() Funktion, die die CopyTicks() Funktion verwendet.

Punkt 6. Erinnern der letzten Tick-Zeit. Dies ist die Zeit des letzten Ticks, erhalten in p. 5 oder nach der Historienberechnung. Die Tick-Historie wird ab diesem Zeitpunkt beim nächsten Start des Indikators abgefragt.

Punkt 7. Berechnung in Echtzeit. Wie bereits erwähnt, wird die Prozedur CalculateCurrentBar() sowohl während der Verlaufs- als auch der Echtzeitberechnung verwendet. Dafür ist das Flag firstLaunch verantwortlich. In diesem Fall wird es auf "false" gesetzt.

7. Merkmale der Arbeit im Strategietester

Bei der Verwendung des Strategie-Testers sollten wir immer bedenken, dass es sich um ein separates Programm mit eigener Funktionalität handelt. Selbst wenn der Tester das Gleiche wie das Terminal tun kann, bedeutet dies nicht, dass er es auf die gleiche Weise wie das Terminal tun wird. Eine ähnliche Situation (in diesem Stadium der Testerentwicklung) tritt bei Programmen auf, die Tickdaten verwenden. Trotz der Tatsache, dass die Anzeige richtig berechnet wurde (die Volumenkontrolle ist erfolgreich), wird der Indikator im Tester etwas anders berechnet. Der Grund dafür liegt wiederum in der Handhabung von Tick-Bündel.

Im Gegensatz zum Terminal, wo mehrere Transaktionen innerhalb eines einzigen Tick auftreten können (d.h. wir können ein Tick-Bündel erhalten), wird im Tester jeder Tick separat empfangen, auch wenn mehrere Ticks eines einzelnen Bündels nacheinander ankommen. Sie können sich davon selbst überzeugen, indem Sie den Testindikator test_tickPack aus der Anwendung heraus starten. Das ungefähre Ergebnis ist wie folgt:

2018.07.13 10:00:00   OnCalculate: Received ticks 4. [0] = 2018.07.13 10:00:00.564, [3] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 2. [0] = 2018.07.13 10:00:00.571, [1] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 3. [0] = 2018.07.13 10:00:00.571, [2] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 4. [0] = 2018.07.13 10:00:00.571, [3] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 5. [0] = 2018.07.13 10:00:00.571, [4] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 6. [0] = 2018.07.13 10:00:00.571, [5] = 2018.07.13 10:00:00.571 FLAG_BUY
2018.07.13 10:00:00   OnCalculate: Received ticks 7. [0] = 2018.07.13 10:00:00.571, [6] = 2018.07.13 10:00:00.572 FLAG_BUY

Jeder kann das selbst ausprobieren. Stellen Sie sicher, dass Sie den Modus "Every tick based on real ticks" einstellen, indem Sie F12 mehrmals drücken. Ticks werden nur einzeln hinzugefügt! In Wirklichkeit könnte dieses Bündel jedoch in Teilen oder in einem Stück in das Terminal gelangen, aber höchstwahrscheinlich nicht im Modus "Einer nach dem anderen". Das ist weder gut noch schlecht. Man behalte diese Funktion einfach im Hinterkopf.

Schlussfolgerung

In diesem Artikel habe ich einige subtile Aspekte und die häufigsten Schwierigkeiten beschrieben, auf die viele Benutzer, darunter auch ich, bei der Entwicklung von Tick-Indikatoren stoßen. Ich hoffe, dass meine Erfahrungen für die Community von Nutzen sein werden, um weitere Anwendungen mit Tickdaten zu ermöglichen und Impulse für die Weiterentwicklung des MetaTrader 5 zu geben. Wenn Sie andere Besonderheiten bei der Arbeit mit Handels-Ticks kennen oder eine Ungenauigkeit festgestellt haben, können Sie mich gerne kontaktieren. Ich würde mich freuen, dieses Thema zu diskutieren.

Das Endergebnis wird im Folgenden dargestellt. Ein blauer Balken zeigt die Dominanz der Käufer einer bestimmten Kerze an, während ein roter Balken — die Dominanz der Verkäufer.


Abb. 5. Delta-Anzeige auf RTS-6.18

Die Schätzung der realen Volumina eröffnet neue Horizonte für die Börsenanalyse und ermöglicht ein besseres Verständnis der Preisbewegung. Dieser Indikator ist nur ein kleiner Teil dessen, was auf der Grundlage der Tickdatenanalyse entwickelt werden kann. Das Erstellen von Aktienindikatoren auf der Grundlage realer Volumina ist eine durchaus machbare Aufgabe. Ich hoffe, dieser Artikel wird Ihnen helfen, solche Indikatoren zu erstellen und Ihren Handel zu verbessern.

Wenn Sie sich für den Indikator selbst interessieren, wird seine verbesserte Version in Kürze im Bereich Produkte in meinem Profil veröffentlicht. Viel Glück beim Handel!

Im Artikel verwendete Dateien

Dateiname Typ Beschreibung
 1. Delta_article.mq5  Indikatordatei  Implementation des Delta-Indikators
 2. Ticks_article.mqh  Klassendatei  Hilfsklasse für die Arbeit mit den Tickdaten
 3. test_getTicksRange.mq5  Script Datei  Testskript für die Prüfung auf die Möglichkeit mehrere Tick-Bündel in einer Millisekunde zu erhalten
 4. test_tickPack.mq5  Indikatordatei  Testindikator für die Prüfung, ob der Tester Ticks erhält


Übersetzt aus dem Russischen von MetaQuotes Software Corp.
Originalartikel: https://www.mql5.com/ru/articles/3708

Beigefügte Dateien |
MQL5.zip (16.21 KB)
Ein universeller RSI-Indikator für das gleichzeitiges Arbeiten in beiden Richtungen Ein universeller RSI-Indikator für das gleichzeitiges Arbeiten in beiden Richtungen

Bei der Entwicklung von Handelsalgorithmen stoßen wir oft auf ein Problem: Wie kann man feststellen, wo ein Trend oder eine Seitwärtsbewegung beginnt und endet? In diesem Artikel versuchen wir, einen universellen Indikator zu erstellen, in dem wir versuchen, Signale für verschiedene Arten von Strategien zu kombinieren. Wir werden versuchen, den Prozess der Beschaffung von Handelssignalen bei einem Experten so weit wie möglich zu vereinfachen. Ein Beispiel für die Kombination mehrerer Indikatoren in einem einzigen wird ausgearbeitet.

950 Webseiten offerieren den Wirtschaftskalender von MetaQuotes 950 Webseiten offerieren den Wirtschaftskalender von MetaQuotes

Mit dem Kalender bieten Webseiten einen detaillierten Zeitplan der Veröffentlichung von 500 Indikatoren und Indizes der größten Volkswirtschaften der Welt. So erhalten Händler schnell die aktuellen Informationen über alle wichtigen Ereignisse mit Erklärungen und Grafiken, zusätzlich zu den wichtigsten Inhalten der jeweiligen Webseite.

50.000 ausgeführte Aufträge im Rahmen des Freelance-Service bei MQL5.com 50.000 ausgeführte Aufträge im Rahmen des Freelance-Service bei MQL5.com

Mitglieder des offiziellen MetaTrader Freelance-Service haben bis Oktober 2018 mehr als 50.000 Aufträge ausgeführt. Dies ist die weltweit größte Freelance-Website für MQL-Programmierer: mehr als tausend Entwickler, Dutzende neuer Aufträge täglich und 7 Sprachlokalisierungen.

Tiefe Neuronale Netzwerke (Teil VIII). Erhöhung der Klassifizierungsqualität von Ensembles mit Bagging Tiefe Neuronale Netzwerke (Teil VIII). Erhöhung der Klassifizierungsqualität von Ensembles mit Bagging

Der Artikel betrachtet drei Methoden, die zur Erhöhung der Klassifizierungsqualität von Ensembles mit Bagging eingesetzt werden können, und schätzt deren Effizienz. Die Auswirkungen der Optimierung der Hyperparameter des neuronalen ELM-Netzwerkes und der Nachbearbeitungsparameter werden bewertet.