English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Schnelles Testen von Handelsideen im Diagramm

Schnelles Testen von Handelsideen im Diagramm

MetaTrader 5Handelssysteme | 5 Mai 2016, 13:57
1 026 0
Vladimir Kustikov
Vladimir Kustikov

Einleitung

Das sechste Automated Trading Championship hat endlich begonnen. Die erste Aufregung ist vorbei und wir können uns endlich ein wenig entspannen und die eingesendeten Handelsroboter untersuchen. Ich habe beschlossen, ein wenig zu recherchieren, um die auffälligsten Merkmale moderner Handelsroboter zu finden und zu bestimmen, was wir von ihrer Handelsaktivität erwarten können.

Das hat sich als hinreichend schwierig erwiesen. Deshalb können meine Berechnungen nicht als absolut genau oder vollständig betrachtet werden, da Beschreibungen von Expert Advisors und gelegentliche Kommentare der Entwickler alles waren, was ich hatte. Allerdings können wir immer noch einige Schlüsse ziehen und nachfolgend finden Sie die Ergebnisse meiner Berechnungen: 451 Expert Advisors nehmen an der Meisterschaft teil, doch nur 316 von ihnen beinhalten nützliche Beschreibungen. Die Entwickler der restlichen EAs haben ihre Beschreibungen mit Grüßen an ihre Freunde und Familien, Nachrichten an außerirdische Zivilisationen oder Selbstbeweihräucherung ausgefüllt.

Die beliebtesten Strategien im ATC 2012:

  • Handel mithilfe diverser grafischer Konstruktionen (wichtige Preisniveaus, Unterstützungs- und Widerstandsniveaus, Kanäle) – 55;
  • Analyse von Preisbewegungen (für verschiedene Timeframes) – 33;
  • Trendverfolgungssysteme (ich denke, dass dieses große Wort eine überoptimierte Kombination von gleitenden Mittelwerten verbirgt, doch ich kann mich auch täuschen :) ) – 31;
  • statistische Preismuster – 10;
  • Arbitration, Analyse von Korrelationen von Symbolen – 8;
  • Volatilitätsanalyse – 8;
  • neuronale Netzwerke – 7;
  • Kerzenanalyse – 5;
  • Mittelwertbildner – 5;
  • Strategiepakete – 5;
  • Sitzungsdauer des Handels – 4;
  • Zufallszahlgenerator – 4;
  • Handel nach Neuigkeiten – 3;
  • Elliott-Wellen – 2.

Indikatorstrategien sind natürlich traditionell die beliebtesten. Es ist schwierig, die Rolle jedes einzelnen Indikators in einem bestimmten Expert Advisors zu definieren, doch es ist möglich, ihre absoluten Nutzungszahlen zu schätzen:

  • Moving Average – 75;
  • MACD – 54;
  • Stochastic Oscillator – 25;
  • RSI – 23;
  • Bollinger Bands – 19;
  • Fractals – 8;
  • CCI, ATR – je 7;
  • Zigzag, Parabolic SAR – je 6;
  • ADX – 5;
  • Momentum – 4;
  • benutzerdefinierte Indikatoren (wie faszinierend :) ) – 4;
  • Ichimoku, AO – je 3;
  • ROC, WPR, StdDev, Volumes – je 2.

Diese Daten legen die folgenden Schlussfolgerungen nahe: Die meisten Teilnehmer befolgen beim Handel Strategien mit Indikatoren. Vielleicht habe ich beim Sammeln der Daten etwas ausgelassen und wir erleben den Aufstieg herausragender Persönlichkeiten auf dem Gebiet des automatisierten Handels, doch das scheint unwahrscheinlich. Ich denke, das Hauptproblem ist, dass Neueinsteiger, die von dem Markt angezogen werden, sich in den meisten Fällen Regeln statt Wissen aneignen.

Hier hast du die Regeln für die Nutzung von MACD, hier sind die Signale. Optimiere jetzt die Parameter und verdiene Geld. Selbst denken? Unsinn! Die Standards wurden bereits entwickelt! Warum das Rad neu erfinden? Allerdings vergessen wir oft, dass die Indikatoren, die heute so beliebt sind, auch von Händlern wie Ihnen und mir erfunden wurden. Auch sie hatten ihre Standards und ihre Autoritätspersonen. Vielleicht wird ja ein neuer Indikator mit Ihrem Namen in zehn Jahren zum Standard.

Ich möchte meine Methode zur Suche nach Handelsideen sowie die von mir genutzte Methode zum schnellen Testen dieser Ideen mit Ihnen teilen.


Beschreibung der Methode

Alle technischen Analysen basieren auf einem einfachen Axiom: Der Preis berücksichtigt alles. Doch es gibt ein Problem – dieser Aussage mangelt es an Dynamik. Wir sehen das Diagramm an und sehen ein statisches Bild: Der Preis hat tatsächlich alles berücksichtigt. Allerdings wollen wir wissen, was der Preis in einer bestimmten Zeit in der Zukunft berücksichtigen und wie er sich bewegen wird, sodass wir Gewinn machen können. Die vom Preis abgeleiteten Indikatoren wurden genau dafür ausgelegt, mögliche zukünftige Bewegungen zu prognostizieren.

Wie wir aus der Physik wissen, ist Geschwindigkeit die erste Ableitung aus der Größe. Deshalb berechnen die Indikatoren die aktuelle Geschwindigkeit der Preisveränderung. Wir wissen auch, dass bedeutende Größen eine Trägheit haben, die eine starke Veränderung der Geschwindigkeit ohne die Einwirkung wesentlicher äußerer Kräfte verhindern. So nähern wir uns allmählich dem Konzept des Trends – des Zustands des Preises, bei dem seine erste Ableitung (Geschwindigkeit) ihren Wert während der Zeit, in der äußere Kräfte (Nachrichten, Beschlüsse von Zentralbanken usw.) nicht darauf einwirken, beibehält.

Doch kehren wir zu unserer Ausgangsthese zurück – der Preis berücksichtigt alles. Um neue Ideen entwickeln zu können, sollten wir das Verhalten des Preises und seiner Ableitungen im gleichen Zeitraum untersuchen. Nur eine sorgfältige Betrachtung von Preisdiagrammen hebt das Niveau Ihres Handels von blindem Glauben zu echtem Verständnis.

Das führt vielleicht nicht zu sofortigen Veränderungen der Handelsergebnisse, aber die Möglichkeit, zahlreiche Warum-Fragen zu beantworten, wird sich früher oder später als nützlich erweisen. Außerdem lässt Sie die visuelle Analyse von Diagrammen und Indikatoren einige ganz neue Zusammenhänge zwischen Preisen und Indikatoren erkennen, die von ihren Entwicklern überhaupt nicht beabsichtigt waren.

Nehmen wir an, Sie haben einen neuen Zusammenhang gefunden, der für Sie günstig zu sein scheint. Was folgt als Nächstes? Der einfachste Weg ist es, einen Expert Advisor zu schreiben und ihn auf historischen Daten zu testen, um sicherzustellen, dass Ihre Annahme korrekt ist. Ist das nicht der Fall, müssen wir uns für den herkömmlichen Weg entscheiden und Parameter optimieren. Das Schlimmste daran ist, dass wir nicht in der Lage waren, die Warum-Frage zu beantworten. Warum hat sich unser Expert Advisor als verlust-/gewinnbringend erwiesen? Warum gab es einen so hohen Wertverlust? Ohne die Antworten zu kennen, werden Sie Ihre Idee nicht effizient umsetzen können.

Ich führe die folgenden Aktionen aus, um die Ergebnisse eines gefundenen Zusammenhangs gleich im Diagramm zu visualisieren:

  1. Ich erstelle oder verändere den erforderlichen Indikator, sodass er ein Signal erzeugt: -1 für Verkaufen und 1 für Kaufen.
  2. Ich verbinde den Bilanzindikator, der Ein- und Austrittspunkte des Diagramms anzeigt. Der Indikator zeigt auch die Veränderung der Bilanz und des Eigenkapitals (in Punkten) an, während er das Signal verarbeitet.
  3. Ich analysiere, in welchen Fällen und unter welchen Bedingungen meine Annahmen korrekt sind.

Diese Methode hat unbestreitbare Vorteile.

  • Erstens: Der Bilanzindikator wird vollständig mithilfe der Methode OnCalculate berechnet, wodurch die höchstmögliche Berechnungsgeschwindigkeit und automatische Verfügbarkeit historischer Daten in den Eingangsberechnungs-Arrays sichergestellt wird.
  • Zweitens: Das Hinzufügen des Signals zum bestehenden Indikator ist ein Zwischenschritt zwischen der Erstellung eines Expert Advisors mithilfe des Wizards und der eigenständigen Entwicklung.
  • Drittens: Die Idee und das Endergebnis sind in einem Diagramm sichtbar. Natürlich hat diese Methode auch gewisse Einschränkungen: Signale sind an den Schließungspreis des Balkens gebunden, die Bilanz wird für ein konstantes Los berechnet, es gibt keine Optionen für den Handel mithilfe von Pending Orders. Doch all diese Einschränkungen können einfach behoben/eingegrenzt werden.


Umsetzung

Lassen Sie uns einen einfachen Signalindikator entwickeln, um zu verstehen, wie er funktioniert, und zu bewerten, wie praktisch diese Methode ist. Ich habe schon vor langer Zeit von Kerzenmustern gehört. Also könnte ihre Arbeit doch in der Praxis getestet werden. Ich habe mich für die Umkehrmuster "Hammer" und "Sternschnuppe" als Kauf- bzw. Verkaufssignale entschieden. Die nachfolgenden Abbildungen zeigen ihr Schema:

Abbildung 1. Kerzenmuster "Hammer" und "Sternschnuppe"

Abbildung 1. Kerzenmuster "Hammer" und "Sternschnuppe"

Definieren wir nun die Regeln für den Markteintritt, wenn das "Hammer"-Muster erscheint.

  1. Der Mindestwert der Kerze muss niedriger sein als die der fünf vorhergehenden Kerzen;
  2. Der Körper der Kerze darf nicht mehr als 50 % ihrer Gesamthöhe darstellen;
  3. Der obere Schatten der Kerze darf nicht mehr als 0% ihrer Gesamthöhe darstellen;
  4. Die Höhe der Kerze darf nicht weniger als 100 % der durchschnittlichen Höhe der fünf Kerzen vor ihr betragen;
  5. Der Schließungspreis des Musters muss niedriger sein als der gleitende Mittelwert mit Zeitraum 10.

Wenn diese Bedingungen erfüllt werden, sollten wir eine lange Position öffnen. Die Regeln für das Muster "Sternschnuppe" sind die gleichen. Der einzige Unterschied ist, dass wir eine kurze Position öffnen sollten:

  1. Der Höchstwert der Kerze muss höher sein als die der fünf vorhergehenden Kerzen;
  2. Der Körper der Kerze darf nicht mehr als 50 % ihrer Gesamthöhe darstellen;
  3. Der untere Schatten der Kerze darf nicht mehr als 0 % ihrer Gesamthöhe darstellen;
  4. Die Höhe der Kerze darf nicht weniger als 100 % der durchschnittlichen Höhe der fünf Kerzen vor ihr betragen;
  5. Der Schließungspreis des Musters muss höher sein als der gleitende Mittelwert mit Zeitraum 10.

Ich habe Parameter fett markiert, die ich auf Basis von Zeichnungen verwendet habe und die in Zukunft optimiert werden können (sofern die Muster akzeptable Ergebnisse zeigen). Die Beschränkungen, die ich implementieren möchte, ermöglichen es uns, Muster mit ungeeigneter Erscheinungsform auszusortieren (S. 1-3) sowie solche, die bekanntermaßen schwach sind und nicht als Signale akzeptiert werden können.

Außerdem müssen wir die Austrittszeitpunkte festlegen. Da die beschriebenen Muster als Trendumkehrsignale erscheinen, existiert der Trend zu dem Zeitpunkt, zu dem die jeweilige Kerze erscheint. Deshalb wird auch der den Preis einholende gleitende Mittelwert vorhanden sein. Das Austrittssignal wird durch die Überkreuzung des Preises und dessen gleitenden Mittelwerts mit Zeitraum 10 geformt.

Jetzt muss ein wenig programmiert werden. Lassen Sie uns einen neuen benutzerdefinierten Indikator im MQL5 Wizard entwickeln, ihn PivotCandles nennen und sein Verhalten beschreiben. Definieren wir die ausgegebenen Werte, um den Bilanzindikator zu verbinden:

  • -1 – Öffnen einer Verkaufsposition;
  • -2 – Schließen einer Kaufposition;
  • 0 – kein Signal;
  • 1 – Öffnen einer Kaufposition;
  • 2 – Schließen einer Verkaufsposition.

Doch wie Sie wissen, suchen echte Programmierer nicht nach einfachen Wegen. Sie suchen nach den einfachsten. :) Ich bin da auch keine Ausnahme. Mit Musik über meine Kopfhörer und mit einer Tasse aromatischen Kaffee erstellte ich die Datei mit der Klasse, die in einem Indikator und einem Expert Advisor umgesetzt werden soll (falls ich beschließe, sie auf Basis des Indikators zu entwickeln). Vielleicht lässt sie sich sogar für andere Kerzenmuster modifizieren. Der Code beinhaltet nichts völlig Neues. Ich glaube, dass die Kommentare zum Code alle möglichen Fragen abdecken.

//+------------------------------------------------------------------+
//|                                            PivotCandlesClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int      iMaxBodySize            = 50;  // Maximum candle body, %
input int      iMaxShadowSize          = 0;   // Maximum allowed candle shadow, %
input int      iVolatilityCandlesCount = 5;   // Number of previous bars for calculation of an average volatility
input int      iPrevCandlesCount       = 5;   // Number of previous bars, for which the current bar should be an extremum
input int      iVolatilityPercent      = 100; // Correlation of a signal candle with a previous volatility, %
input int      iMAPeriod               = 10;  // Period of a simple signal moving average
//+------------------------------------------------------------------+
//| Class definition                                                 |
//+------------------------------------------------------------------+
class CPivotCandlesClass
  {
private:
   MqlRates          m_candles[];              // Array for storing the history necessary for calculations
   int               m_history_depth;          // Array length for storing the history
   int               m_handled_candles_count;  // Number of the already processed candles
   
   double            m_ma_value;               // Current calculated moving average value
   double            m_prev_ma_value;          // Previous calculated moving average value
   bool              m_is_highest;             // Check if the current candle is the highest one
   bool              m_is_lowest;              // Check if the current candle is the lowest one
   double            m_volatility;             // Average volatility
   int               m_candle_pattern;         // Current recognized pattern
   
   void              PrepareArrayForNewCandle();        // Prepare the array for accepting the new candle
   int               CheckCandleSize(MqlRates &candle); // Check the candle for conformity with patterns
   void              PrepareCalculation();
protected:
   int               DoAnalizeNewCandle();              // Calculation function
public:
   void              CPivotCandlesClass(); 
   
   void              CleanupHistory();                  // Clean up all calculation variables  
   double            MAValue() {return m_ma_value;}     // Current value of the moving average
   int               AnalizeNewCandle(MqlRates& candle);
   int               AnalizeNewCandle( 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 );
  };
//+------------------------------------------------------------------+
//| CPivotCandlesClass                                               |
//+------------------------------------------------------------------+
//| Class initialization                                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::CPivotCandlesClass()
  {
   // History depth should be enough for all calculations
   m_history_depth = (int)MathMax(MathMax(
      iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod);
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;
   
   ArrayResize(m_candles, m_history_depth);
  }  
//+------------------------------------------------------------------+
//| CleanupHistory                                                   |
//+------------------------------------------------------------------+
//| Clean up the candle buffer for recalculation                     |
//+------------------------------------------------------------------+
void CPivotCandlesClass::CleanupHistory()
  {
   // Clean up the array
   ArrayFree(m_candles);
   ArrayResize(m_candles, m_history_depth);
   
   // Null calculation variables
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;   
  }
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on candle's separate parameter values                       |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle( 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 )
  {
   // Prepare the array for the new candle
   PrepareArrayForNewCandle();

   // Fill out the current value of the candle
   m_candles[0].time          = time;
   m_candles[0].open          = open;
   m_candles[0].high          = high;
   m_candles[0].low           = low;
   m_candles[0].close         = close;
   m_candles[0].tick_volume   = tick_volume;
   m_candles[0].real_volume   = volume;
   m_candles[0].spread        = spread;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }  
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on the received candle                                      |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle)
  {
   // Prepare the array for the new candle   
   PrepareArrayForNewCandle();

   // Add the candle 
   m_candles[0] = candle;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }
//+------------------------------------------------------------------+
//| PrepareArrayForNewCandle                                         |
//+------------------------------------------------------------------+ 
//| Prepare the array for the new candle                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareArrayForNewCandle()
  {
   // Shift the array by one position to write the new value there
   ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1);
   
   // Increase the counter of added candles
   m_handled_candles_count++;
  }
//+------------------------------------------------------------------+
//| CalcMAValue                                                      |
//+------------------------------------------------------------------+ 
//| Calculate the current values of the Moving Average, volatility   |
//|   and the value extremality                                      |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareCalculation()
  {
   // Store the previous value
   m_prev_ma_value = m_ma_value;
   m_ma_value = 0;
   
   m_is_highest = true; 	// check if the current candle is the highest one
   m_is_lowest = true;  	// check if the current candle is the lowest one
   m_volatility = 0;  	// average volatility
   
   double price_sum = 0; // Variable for storing the sum
   for (int i=0; i<m_history_depth; i++)
     {
      if (i<iMAPeriod)
         price_sum += m_candles[i].close;
      if (i>0 && i<=iVolatilityCandlesCount)
         m_volatility += m_candles[i].high - m_candles[i].low;
      if (i>0 && i<=iPrevCandlesCount)
        {
         m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high);
         m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low);
        }
     }
   m_ma_value = price_sum / iMAPeriod;
   m_volatility /= iVolatilityCandlesCount;
   
   m_candle_pattern = CheckCandleSize(m_candles[0]);
  }
//+------------------------------------------------------------------+
//| CheckCandleSize                                                  |
//+------------------------------------------------------------------+
//| Check if the candle sizes comply with the patterns               |
//| The function returns:                                            |
//|   0 - if the candle does not comply with the patterns            |
//|   1 - if "hammer" pattern is detected                            |
//|   -1 - if "shooting star" pattern is detected                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::CheckCandleSize(MqlRates &candle)
  {
   double candle_height=candle.high-candle.low;          // candle's full height
   double candle_body=MathAbs(candle.close-candle.open); // candle's body height

   // Check if the candle has a small body
   if(candle_body/candle_height*100.0>iMaxBodySize)
      return 0;

   double candle_top_shadow=candle.high-MathMax(candle.open,candle.close);   // candle upper shadow height
   double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // candle bottom shadow height

   // If the upper shadow is very small, that indicates the "hammer" pattern
   if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize)
      return 1;
   // If the bottom shadow is very small, that indicates the "shooting star" pattern
   else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize)
      return -1;
   else
      return 0;
  }
//+------------------------------------------------------------------+
//| DoAnalizeNewCandle                                               |
//+------------------------------------------------------------------+
//| Real analysis of compliance with the patterns                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::DoAnalizeNewCandle()
  {
   // Prepare data for analyzing the current situation
   PrepareCalculation();
   
   // Process prepared data and set the exit signal
   int signal = 0;
   
   ///////////////////////////////////////////////////////////////////
   // EXIT SIGNALS                                                  //
   ///////////////////////////////////////////////////////////////////
   // If price crosses the moving average downwards, short position is closed
   if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value)
      signal = 2;
   // If price crosses the moving average upwards, long position is closed 
   else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value)
      signal = -2;
      
   ///////////////////////////////////////////////////////////////////
   // ENTRY SIGNALS                                                 //
   ///////////////////////////////////////////////////////////////////
   // Check if the minimum volatility condition is met
   if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility)
     {
      // Checks for "shooting star" pattern
      if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value)
         signal = -1;
      // Checks for "hammer" pattern
      else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value)
         signal = 1;
     }
     
   return signal;
  }
//+------------------------------------------------------------------+

Wir können sehen, dass der gesamte Berechnungsteil durch die Klasse CPivotCandlesClass durchgeführt wird. Es gilt als guter Stil in der Programmierung, den Berechnungsteil vom visuellen Teil zu trennen, und ich tue mein Bestes, um dieser Empfehlung zu folgen. Die Gewinne lassen nicht lang auf sich warten – nachfolgend sehen Sie den Code des Indikators selbst:

//+------------------------------------------------------------------+
//|                                                 PivotCandles.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

// Use four buffers, while drawing two
#property indicator_buffers 4
#property indicator_plots   2
//--- plot SlowMA
#property indicator_label1  "SlowMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAliceBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot ChartSignal
#property indicator_label2  "ChartSignal"
#property indicator_type2   DRAW_COLOR_ARROW
#property indicator_color2  clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  3

#include <PivotCandlesClass.mqh>
//+------------------------------------------------------------------+
//| Common arrays and structures                                     |
//+------------------------------------------------------------------+
//--- Indicator buffers                                                
double   SMA[];            // Values of the Moving Average
double   Signal[];         // Signal values
double   ChartSignal[];    // Location of signals on the chart
double   SignalColor[];    // Signal color array
//--- Calculation class
CPivotCandlesClass PivotCandlesClass;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,SMA,INDICATOR_DATA);
   SetIndexBuffer(1,ChartSignal,INDICATOR_DATA);
   SetIndexBuffer(2,SignalColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Signal,INDICATOR_CALCULATIONS);

//--- set 0 as an empty value
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);

   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
   // If there have not been calculations yet or (!) the new history is uploaded, clean up the calculation object
   if (prev_calculated == 0)
      PivotCandlesClass.CleanupHistory();
   
   int end_calc_edge = rates_total-1;   
   if (prev_calculated >= end_calc_edge)
      return end_calc_edge;
   
   for(int i=prev_calculated; i<end_calc_edge; i++)
     {
      int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]);
      Signal[i] = signal;
      SMA[i] = PivotCandlesClass.MAValue();
      
      // Signals are processed, display them on the chart
      // Set the location of our signals...
      if (signal < 0)
         ChartSignal[i]=high[i];
      else if (signal > 0)
         ChartSignal[i]=low[i];
      else
         ChartSignal[i]=0;
      // .. as well as their color
      // Signals have a range of [-2..2], while color indices - [0..4]. Align them 
      SignalColor[i]=signal+2;
     }
   
   // Set the Moving Average value similar to the previous one to prevent it from sharp fall
   SMA[end_calc_edge] = SMA[end_calc_edge-1];

//--- return value of prev_calculated for next call
   return(end_calc_edge);
  }
//+------------------------------------------------------------------+

Der Indikator ist fertig. Testen wir ihn nun auf einem beliebigen Diagramm. Richten Sie hierzu den kompilierten Indikator im Diagramm ein. Anschließend sehen wir etwas in der Art wie in der nachfolgenden Abbildung.

Abbildung 2. Indikator der Kerzenmuster "Hammer" und "Sternschnuppe"

Abbildung 2. Indikator der Kerzenmuster "Hammer" und "Sternschnuppe"

Farbige Punkte zeigen mögliche Marktein- und -austritte an. Die Farben werden folgendermaßen gewählt:

  • Dunkelrot – verkaufen;
  • Dunkelblau – kaufen;
  • Hellrot – lange Position schließen;
  • Hellrot – kurze Position schließen.

Schließungssignale werden jedes Mal geformt, wenn der Preis seinen gleitenden Mittelwert erreicht. Das Signal wird ignoriert, wenn es zu dem Zeitpunkt keine Positionen gab.

Gehen wir nun zum Hauptthema dieses Beitrags über. Wir verfügen über den Indikator mit Signalpuffer, der nur einige bestimmte Signale erzeugt. Bilden wir in einem separaten Fenster desselben Diagramms ab, wie gewinn-/verlustbringend diese Signale sein können, wenn sie tatsächlich befolgt werden. Der Indikator wurde speziell dafür entwickelt. Er kann sich mit einem anderen Indikator verbinden und je nach eingehenden Signalen virtuelle Positionen öffnen/schließen.

Wie beim vorherigen Indikator sollten wir den Code in zwei Teile aufteilen – Berechnung und visueller Teil. Nachfolgend sehen Sie das Ergebnis einer schlaflosen Nacht, doch ich hoffe, dass es das wert ist. :)

//+------------------------------------------------------------------+
//|                                                 BalanceClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Common structures                                                |
//+------------------------------------------------------------------+
// Structure for returning calculation results 
// using only return command;
struct BalanceResults
  {
   double balance;
   double equity;
  };
//+------------------------------------------------------------------+
//| Common function                                                  |
//+------------------------------------------------------------------+
//  Function for searching for the indicator handle by its name
int FindIndicatorHandle(string _name)
  {
   // Receive the number of open charts
   int windowsCount = (int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
   
   // Search all of them
   for(int w=windowsCount-1; w>=0; w--)
     {
      // How many indicators are attached to the current chart
      int indicatorsCount = ChartIndicatorsTotal(0,w);

      // Search by all chart indicators
      for(int i=0;i<indicatorsCount;i++)
        {
         string name = ChartIndicatorName(0,w,i);
         // If such an indicator is found, return its handle
         if (name == _name)
            return ChartIndicatorGet(0,w,name);
        }
     }  
     
   // If there is no such an indicator, return the incorrect handle 
   return -1;
  }
//+------------------------------------------------------------------+
//| Base calculation class                                           |
//+------------------------------------------------------------------+
class CBaseBalanceCalculator
  {
private:
   double            m_position_volume; // Current open position volume
   double            m_position_price;  // Position opening price
   double            m_symbol_points;   // Value of one point for the current symbol
   BalanceResults    m_results;         // Calculation results
public:
   void              CBaseBalanceCalculator(string symbol_name = "");
   void              Cleanup();
   BalanceResults    Calculate( const double _prev_balance, 
                                const int    _signal,
                                const double _next_open,
                                const double _next_spread );
  };
//+------------------------------------------------------------------+
//| CBaseBalanceCalculator                                           |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::CBaseBalanceCalculator(string symbol_name = "")
  {
   // Clean up state variables
   Cleanup();
   
   // Define point size (because we will calculate the profit in points)
   if (symbol_name == "")
      m_symbol_points = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   else 
      m_symbol_points = SymbolInfoDouble(symbol_name, SYMBOL_POINT);
  }
//+------------------------------------------------------------------+
//| Cleanup                                                          |
//+------------------------------------------------------------------+
//| Clean up data on positions and prices                            |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::Cleanup()
  {
   m_position_volume = 0;
   m_position_price = 0;  
  }
//+------------------------------------------------------------------+
//| Calculate                                                        |
//+------------------------------------------------------------------+
//| Main calculation block                                           |
//+------------------------------------------------------------------+
BalanceResults CBaseBalanceCalculator::Calculate(
                                       const double _prev_balance,
                                       const int _signal,
                                       const double _next_open,
                                       const double _next_spread )
  {
   // Clean up the output structure from the previous values
   ZeroMemory(m_results);
   
   // Initialize additional variables
   double current_price = 0; // current price (bid or ask depending on position direction)
   double profit = 0;        // profit calculated value
   
   // If there was no signal, the balance remains the same 
   if (_signal == 0)
      m_results.balance = _prev_balance;
   // the signal coincides with the direction or no positions are opened yet
   else if (_signal * m_position_volume >= 0)
     {
      // Position already exists, the signal is ignored
      if (m_position_volume != 0)
         // Balance is not changed
         m_results.balance = _prev_balance;
      // No positions yet, buy signal

      else if (_signal == 1)
        {
         // Calculate current ASK price, recalculate price, volume and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         m_position_price = (m_position_volume * m_position_price + current_price) / (m_position_volume + 1);
         m_position_volume = m_position_volume + 1;
         m_results.balance = _prev_balance;
        }
      // No positions yet, sell signal
      else if (_signal == -1) 
        {
         // Calculate current BID price, recalculate price, volume and balance
         current_price = _next_open;
         m_position_price = (-m_position_volume * m_position_price + current_price) / (-m_position_volume + 1);
         m_position_volume = m_position_volume - 1; 
         m_results.balance = _prev_balance;      
        }
      else
         m_results.balance = _prev_balance;
     }
   // Position is set already, the opposite direction signal is received
   else 
     {
      // buy signal/close sell position
      if (_signal > 0)
        {
         // Close position by ASK price, recalculate profit and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
          
         // If there is a signal for opening a new position, open it at once
         if (_signal == 1)
           {
            m_position_price = current_price;
            m_position_volume = 1;
           }
         else
            m_position_volume = 0;
        }
      // sell signal/close buy position
      else 
        {
         // Close position by BID price, recalculate profit and balance
         current_price = _next_open;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
         
         // If there is a signal for opening a new position, open it at once
         if (_signal == -1)
           {
            m_position_price = current_price;
            m_position_volume = -1;
           }
         else 
           m_position_volume = 0;
        }
     }
    
   // Calculate the current equity
   if (m_position_volume > 0)
     {
      current_price = _next_open;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else if (m_position_volume < 0)
     {
      current_price = _next_open + _next_spread * m_symbol_points;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else
      m_results.equity = m_results.balance;    
   
   return m_results;
  }
//+------------------------------------------------------------------+

Die Berechnungsklasse ist fertig. Nun müssen wir die Anzeige des Indikators umsetzen, um zu sehen, wie er arbeitet.

//+------------------------------------------------------------------+
//|                                                      Balance.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window

#property indicator_buffers 4
#property indicator_plots   3
#property indicator_level1  0.0 
#property indicator_levelcolor Silver 
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1  
//--- plot Balance
#property indicator_label1  "Balance"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrBlue,clrRed
#property indicator_style1  STYLE_DOT
#property indicator_width1  1
//--- plot Equity
#property indicator_label2  "Equity"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLime
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Zero
#property indicator_label3  "Zero"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#include <BalanceClass.mqh>
//+------------------------------------------------------------------+
//| Input and global variables                                       |
//+------------------------------------------------------------------+
input string   iParentName        = "";             // Indicator name for balance calculation
input int      iSignalBufferIndex = -1;            // Signal buffer's index number
input datetime iStartTime         = D'01.01.2012';  // Calculation start date
input datetime iEndTime           = 0;             // Calculation end date
//--- Indicator buffers 
double   Balance[];       // Balance values
double   BalanceColor[];  // Color index for drawing the balance
double   Equity[];        // Equity values
double   Zero[];          // Zero value for histogram's correct display
//--- Global variables
double   Signal[1];       // Array for receiving the current signal
int      parent_handle;   // Indicator handle, the signals of which are to be used 

CBaseBalanceCalculator calculator; // Object for calculating balance and equity
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {     
   // Binding indicator buffers
   SetIndexBuffer(0,Balance,INDICATOR_DATA);
   SetIndexBuffer(1,BalanceColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,Equity,INDICATOR_DATA);
   SetIndexBuffer(3,Zero,INDICATOR_DATA);
  
   // Search for indicator handle by its name
   parent_handle = FindIndicatorHandle(iParentName);
   if (parent_handle < 0)
     {
      Print("Error! Parent indicator not found");
      return -1;
     } 
   
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {   
   // Set the borders for calculating the indicator
   int start_index = prev_calculated;
   int end_index = rates_total-1;
   
   // Calculate balance and equity values
   for(int i=start_index; i<end_index; i++)
     {
      // Check if the balance calculation corresponds the interval
      if (time[i] < iStartTime)
        {
         Balance[i] = 0;
         Equity[i] = 0; 
         continue;
        }
      if (time[i] > iEndTime && iEndTime != 0)
        {
         Equity[i] = (i==0) ? 0 : Equity[i-1];
         Balance[i] = Equity[i]; 
         continue;
        }
      
      // Request a signal from the parent indicator
      if(CopyBuffer(parent_handle,iSignalBufferIndex,time[i],1,Signal)==-1) // Copy the indicator main line data
        {
         Print("Data copy error: " + IntegerToString(GetLastError()));
         return(0);  // Finish the function operation and send indicator for the full recalculation
        }
      
      // Initialize balance and equity calculation
      // Since the signal is formed when the candle is closing, we will be able 
      //   to perform any operation only at the next candle's opening price
      BalanceResults results = calculator.Calculate(i==0?0:Balance[i-1], (int)Signal[0], open[i+1], spread[1+1]);
      
      // Fill out all indicator buffers
      Balance[i] = results.balance;
      Equity[i] = results.equity; 
      Zero[i] = 0;
      if (Balance[i] >= 0)
         BalanceColor[i] = 0;
      else
         BalanceColor[i] = 1;
     }
     
   // Fill out buffers for the last candle 
   Balance[end_index] = Balance[end_index-1];
   Equity[end_index] = Equity[end_index-1]; 
   BalanceColor[end_index] = BalanceColor[end_index-1];
   Zero[end_index] = 0;
     
   return rates_total;
  }
//+------------------------------------------------------------------+

Es ist geschafft! Kompilieren wir ihn und betrachten die Ergebnisse.


Anweisungen für den Gebrauch

Um die Arbeit unseres neu entwickelten Indikators zu bewerten, muss er an ein Diagramm mit mindestens einem Signalindikator angehängt werden. Wenn Sie alle Schritte befolgt haben, verfügen wir bereits über einen solchen Indikator: PivotCandles. Nun müssen wir die Eingabeparameter konfigurieren. Wir müssen Folgendes festlegen:

  • Name des Indikators für die Bilanzberechnung (String) – wir müssen daran denken, dass die Anbindung des Bilanzindikators nach dem Namen durchgeführt wird. Deshalb ist dieses Feld verpflichtend.
  • Indexnummer des Signalpuffers (ganze Zahl) – ein weiterer kritischer Parameter. Der Signalindikator kann mehrere Signale gemäß dem vorher definierten Algorithmus erzeugen. Deshalb muss der Bilanzindikator über die Daten bezüglich des zu berechnenden Puffersignals verfügen.
  • Startdatum der Berechnung (Datum/Zeit) – Anfangsdatum der Berechnung der Bilanz.
  • Enddatum der Berechnung (Datum/Zeit) – Enddatum der Berechnung der Bilanz. Falls das Datum nicht ausgewählt wird (gleich Null), wird die Berechnung bis zum letzten Balken ausgeführt.

Abbildung 3 zeigt die Konfiguration der ersten zwei Parameter zum Anhängen des Bilanzindikators an den dritten Puffer des Indikators PivotCandles. Die verbleibenden zwei Parameter können nach Ihrem eigenen Belieben eingestellt werden.

Abbildung 3. Parameter des Indikators Balance

Abbildung 3. Parameter des Indikators Balance

Wenn alle vorhergehenden Schritte korrekt ausgeführt wurden, sollten Sie ein Bild sehen, dass dem unten aufgeführten sehr ähnlich ist.

Abbildung 4. Kurven der Bilanz und des Eigenkapitals, erzeugt mithilfe der Signale des Indikators PivotCandles

Abbildung 4. Kurven der Bilanz und des Eigenkapitals, erzeugt mithilfe der Signale des Indikators PivotCandles

Nun können wir unterschiedliche Timeframes und Symbole ausprobieren und die profitabelsten und verlustreichsten Markteintritte herausfinden. Beachten Sie, dass dieser Ansatz Ihnen hilft, Korrelationen des Marktes zu finden, die sich auf Ihre Handelsergebnisse auswirken.

Ursprünglich wollte ich die Zeit für das Testen des Expert Advisors auf Basis derselben Signale mit der Zeit vergleichen, die für die oben beschriebene Methode benötigt wird. Doch ich habe diese Idee aufgegeben, da die Neuberechnung des Indikators etwa eine Sekunde dauert. Eine so kurze Zeit kann von einem Expert Advisor mit seinen Algorithmen zum Hochladen der Historie und Erzeugen von Ticks sicher noch nicht erreicht werden.


Fazit

Die oben beschriebene Methode ist sehr schnell. Außerdem sorgt sie für Klarheit beim Testen der Indikatoren, die Signale zum Öffnen/Schließen von Positionen erzeugen. Sie ermöglicht es Händlern, die Signale zu analysieren und Reaktionen darauf in einem einzigen Diagrammfenster abzulegen. Doch sie hat ihre Grenzen, die wir uns bewusst machen müssen:

  • der Signalpuffer des zu analysierenden Indikators sollte vorher vorbereitet werden;
  • die Signale sind an die Öffnungszeit des neuen Balkens gebunden;
  • kein MM bei der Berechnung der Bilanz;

Doch ich hoffe, dass die Vorteile trotz dieser Unzulänglichkeiten sich als bedeutend erweisen und dass diese Prüfmethode ihren Platz unter den anderen Werkzeugen zur Analyse des Marktverhaltens und zur Verarbeitung der vom Markt erzeugten Signale finden wird.

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

Beigefügte Dateien |
balanceclass.mqh (8.07 KB)
balance.mq5 (5.57 KB)
pivotcandles.mq5 (4.14 KB)
Handelssignale in MetaTrader 5: Eine bessere Alternative zu PAMM-Kontos! Handelssignale in MetaTrader 5: Eine bessere Alternative zu PAMM-Kontos!
Wir freuen uns, bekannt geben zu können, dass MetaTrader 5 nun über Handelssignale verfügt und Investoren und Managern damit ein leistungsfähiges Werkzeug bietet. Während Sie den Handelsoperationen eines erfolgreichen Händlers folgen, werden sie automatisch vom Terminal in Ihrem Konto reproduziert!
Mit MetaTrader 5 via Named Pipes ohne DDLs kommunizieren Mit MetaTrader 5 via Named Pipes ohne DDLs kommunizieren
Viele Entwickler sehen sich mit demselben Problem konfrontiert: Wie kreiert man eine Sandboxumgebung für ein Handelsterminal ohne unsichere DLLs zu benutzen. Eine der leichtesten und zugleich sichersten Methoden besteht in der Verwendung standardisierter Named Pipes, die normale Dateioperationen gewährleisten. Diese ermöglichen eine Interprozessor-Client-Server basierte Kommunikation zwischen Programmen. Werfen Sie einen Blick auf die praktischen C++- und MQL5-Beispiele, einschließlich Server, Client, den Datenaustausch zwischen beiden sowie den Benchmark-Test.
Wie man Handelssignale abonniert Wie man Handelssignale abonniert
Der Signal-Service zeigt Ihnen, wie sie mit MetaTrader 4 und MetaTrader 5 sozial traden. Dieser Service ist in die Handelsplattform fest integriert und erlaubt es jedermann, mit Leichtigkeit die Aktionen professioneller Trader zu kopieren. Wählen Sie einen der vielen tausend Signalanbieter, schließen Sie mit nur wenigen Klicks ein Abo ab und die Trades dieses Anbieters werden auf Ihr Konto kopiert.
Wie erstelle ich MetaTrader 5-Angebote für andere Applikationen Wie erstelle ich MetaTrader 5-Angebote für andere Applikationen
Dieser Artikel widmet sich Beispielen, die u.a. folgende Dinge veranschaulichen sollen: Verzeichnisse anlegen, Daten kopieren und ablegen, via Market Watch oder mittels der allgemeinen Liste mit Symbolen hantieren, mit Fehlern umgehen, usw. All diese Elemente können letztendlich mit einem Skript erfasst werden, das Daten in einem nutzerfreundlichen Format ablegt.