
Schnelles Testen von Handelsideen im Diagramm
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:
- Ich erstelle oder verändere den erforderlichen Indikator, sodass er ein Signal erzeugt: -1 für Verkaufen und 1 für Kaufen.
- 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.
- 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"
Definieren wir nun die Regeln für den Markteintritt, wenn das "Hammer"-Muster erscheint.
- Der Mindestwert der Kerze muss niedriger sein als die der fünf vorhergehenden Kerzen;
- Der Körper der Kerze darf nicht mehr als 50 % ihrer Gesamthöhe darstellen;
- Der obere Schatten der Kerze darf nicht mehr als 0% ihrer Gesamthöhe darstellen;
- Die Höhe der Kerze darf nicht weniger als 100 % der durchschnittlichen Höhe der fünf Kerzen vor ihr betragen;
- 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:
- Der Höchstwert der Kerze muss höher sein als die der fünf vorhergehenden Kerzen;
- Der Körper der Kerze darf nicht mehr als 50 % ihrer Gesamthöhe darstellen;
- Der untere Schatten der Kerze darf nicht mehr als 0 % ihrer Gesamthöhe darstellen;
- Die Höhe der Kerze darf nicht weniger als 100 % der durchschnittlichen Höhe der fünf Kerzen vor ihr betragen;
- 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"
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
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
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





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.