English Русский 中文 Español 日本語 Português 한국어 Français Italiano Türkçe
Das MQL5-Kochbuch: Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden

Das MQL5-Kochbuch: Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden

MetaTrader 5Beispiele | 20 Mai 2016, 13:13
696 0
Anatoli Kazharski
Anatoli Kazharski

Einleitung

In diesem Beitrag betrachten wir die Entwicklung eines Indikators mit mehreren Symbolen für die Analyse von Preisunterschieden in einem bestimmten Zeitraum. Die wichtigsten Themen wurden bereits im vorhergehenden Beitrag zur Programmierung mehrwährungsfähiger Indikatoren besprochen, Das MQL5-Kochbuch: Entwicklung eines Indikators für die Volatilität mehrerer Symbole in MQL5. Diesmal gehen wir also nur auf jene neuen Features und Funktionen ein, an denen wesentliche Änderungen vorgenommen wurden. Wenn Sie ein Neuling in der Programmierung von mehrwährungsfähigen Indikatoren sind, empfehle ich Ihnen, zuerst den vorherigen Beitrag zu lesen.

Wir werden in diesem Beitrag auf die folgenden Themen eingehen:

  • Ändern der Eigenschaften des Diagramms.
  • Verarbeitung der Ereignisse CHARTEVENT_OBJECT_DRAG (Ziehen eines Diagrammobjekts) und CHARTEVENT_CHART_CHANGE (Ändern der Größe des Diagramms oder der Eigenschaften des Diagramms mithilfe des Dialogfensters mit den Eigenschaften).
  • Darstellung von Indikatorpuffern mit mehr als einer Farbe.
  • Definieren von Maxima und Minima in Indikatorpuffern innerhalb des sichtbaren Bereichs zum Festlegen eines Maximums/Minimums des Diagramms.
  • Umkehrung einer Serie.

Die resultierende Codemenge für unseren Indikator ist mit etwa 1500 Zeilen ziemlich groß. Deshalb verteilen wir alle Funktionen in separaten Dateien und verknüpfen sie mit der Hauptdatei des Projekts. Es wird drei Kategorien von Funktionen für externe Dateien geben:

  • Checks.mqh – Funktionen zum Durchführen diverser Prüfungen und zum Herunterladen verfügbarer Daten.
  • Objects.mqh – Funktionen zum Verwalten von grafischen Objekten.
  • Chart.mqh – Funktionen zum Verwalten von Diagrammeigenschaften.

Alle Funktionen, die nicht zu den oben aufgezählten Kategorien gehören, verbleiben in der Hauptdatei.


Entwicklung des Indikators

Fahren wir mit der Entwicklung des Indikators fort. Als Erstes müssen wir ein neues Projekt erstellen. Erstellen Sie hierzu im Verzeichnis MetaTrader 5\MQL5\Indicators einen Ordner mit dem Namen unseres Indikators und darin den Ordner Include, in dem die Include-Dateien abgelegt werden. Als Nächstes erstellen sie die Hauptdatei im Ordner des Indikators. Dies kann manuell durch das Erstellen einer Textdatei mit der Erweiterung *.mq5 oder mithilfe einer Vorlage im MQL5 Wizard bewerkstelligt werden. Zusätzlich zu den Kernfunktionen des Programms, OnInit(), OnDeinit() und OnCalculate(), verwenden wir auch OnChartEvent() und OnTimer().

Wie im vorherigen Beitrag lassen wir neben dem aktuellen Symbol Daten für 5 in den externen Parametern festgelegte Symbole anzeigen. Doch diesmal geben wir anstellte von Werten, die durch eine Formel errechnet wurden, rohe Preisdaten im Diagramm wieder. Dem Benutzer steht es frei, die Art der Darstellung der Daten in den externen Parametern aus der Dropdown-Liste auszuwählen: Linie, Balken oder Kerzen.

Wenn wir die Daten nur als einfarbige Linien darstellen müssten, würde es ausreichen, die Menge der Puffer gleich der Menge der Symbole in den Eigenschaften des Indikators (#property) anzugeben. Doch da es zwei Modi für das Zeichnen von Serien als Balken und Kerzen gibt, brauchen wir mehr Puffer für den zweifarbigen Modus: vier Puffer zum Darstellen jeder Serie und einen Puffer zum Festlegen der Farbe (abhängig von der Bedingung) für jedes Element in einer Grafikserie.

Für jede Serie müssen die Farben in den Programmeigenschaften festgelegt werden. Listen Sie sie dafür einfach durch Kommata getrennt auf. Als Erstes kommt die im einfarbigen Modus verwendete Farbe. Im zweifarbigen Modus wird sie für aufwärts gerichtete Balken/Kerzen genutzt. Die zweite Farbe wird nur im zweifarbigen Modus für abwärts gerichtete Balken/Kerzen verwendet.

Die Codes all dieser Parameter sind nachfolgend aufgeführt:

#property indicator_chart_window // Indicator is in the main window
#property indicator_buffers 25   // Number of buffers for indicator calculation
#property indicator_plots   5    // Number of plotting series
//--- Indicator buffers colors
#property indicator_color1  clrDodgerBlue,C'0,50,100'
#property indicator_color2  clrLimeGreen,C'20,80,20'
#property indicator_color3  clrGold,C'160,140,0'
#property indicator_color4  clrAqua,C'0,140,140'
#property indicator_color5  clrMagenta,C'130,0,130'

Deklarieren wir mit der Direktive #define Konstanten und nehmen mit der Befehlszeile #include Dateien mit Funktionen auf, die bereits oben beschrieben wurden, sowie die Klasse aus der Standardbibliothek zum Arbeiten mit der Arbeitsfläche:

//--- Constants 
#define RESET           0 // Returning the indicator recalculation command to the terminal
#define SYMBOLS_COUNT   5 // Number of symbols
//--- Include the class for working with the canvas
#include <Canvas\Canvas.mqh>
//--- Include the class for working with the canvas
#include "Include/Checks.mqh"
#include "Include/Chart.mqh"
#include "Include/Objects.mqh"

Fügen Sie die Aufzählungen ENUM_DRAWTYPE und ENUM_START_POINT hinzu, um Dropdown-Listen zu erzeugen, die die Auswahl des Darstellungstypen der Preisdaten und den Modus des Ausgangspunkts des Preisunterschieds in den externen Parametern ermöglichen:

//--- Drawing type of the price data
enum ENUM_DRAWTYPE
  {
   LINE   =0,  // Line
   BARS   =1,  // Bars
   CANDLES=2   // Candlesticks
  };
//--- Mode of the price divergence starting point
enum ENUM_START_POINT
  {
   VERTICAL_LINE=0,  // Vertical line
   MONTH        =1,  // Month
   WEEK         =2,  // Week
   DAY          =3,  // Day
   HOUR         =4   // Hour
  };

Die Arten der Darstellung der Daten wurden oben bereits beschrieben, sehen wir uns also näher an, was der Ausgangspunkt des Preisunterschieds bedeutet.

Insgesamt wird es fünf Modi geben: Vertikale Linie, Monat, Woche, Tag und Stunde. Für den Modus Vertikale Linie wird beim Laden des Indikators im Diagramm eine vertikale Linie hinzugefügt. Durch Ziehen dieser Linie bestimmen Sie den Balken, bei dem sich die Preise aller Symbole an einem Punkt treffen. Der Öffnungspreis des festgelegten Balkens für das aktuelle Symbol gilt als Bezugspunkt dieses Treffens. In allen anderen Modi erhält das Programm die Information, dass die Preise sich jedes Mal am Anfang des festgelegten Zeitraums treffen müssen, d. h. am Anfang jedes Monats, am Anfang jeder Woche, am Anfang jedes Tages oder am Anfang jeder Stunde.

Nachfolgend sehen Sie die Liste der Eingabeparameter des Indikators:

//--- External parameters
input  ENUM_DRAWTYPE    DrawType             =CANDLES;       // Drawing type
input  ENUM_START_POINT StartPriceDivergence =VERTICAL_LINE; // Start of price divergence
input  bool             TwoColor             =false;         // Two-color bars/candlesticks
sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
input  string           Symbol02             ="GBPUSD";      // Symbol 2
input  bool             Inverse02            =false;         // Inverse symbol 2
input  string           Symbol03             ="AUDUSD";      // Symbol 3
input  bool             Inverse03            =false;         // Inverse symbol 3
input  string           Symbol04             ="NZDUSD";      // Symbol 4
input  bool             Inverse04            =false;         // Inverse symbol 4
input  string           Symbol05             ="USDCAD";      // Symbol 5
input  bool             Inverse05            =false;         // Inverse symbol 5
input  string           Symbol06             ="USDCHF";      // Symbol 6
input  bool             Inverse06            =false;         // Inverse symbol 6

Symbole werden ab 2 durchnummeriert, da 1 das aktuelle Symbol im Diagramm ist.

Für jedes eingeschlossene Symbol kann die Umkehrung angewendet werden. Umkehrung bedeutet, dass die Daten des Symbols umgedreht dargestellt werden. Das kann nützlich sein, wenn die Liste der analysierten Symbole Währungspaare enthält, in denen eine Währung (beispielsweise US-Dollar) sowohl die Basiswährung als auch die Gegenwährung sein kann. Beispielsweise ist im Währungspaar EURUSD der US-Dollar die Gegenwährung, im Währungspaar USDCHF ist er die Basiswährung. Falls das aktuelle Symbol im Diagramm EURUSD ist, können Sie die Umkehrung für USDCHF aktivieren, wodurch die Darstellung der Preise bequemer zu analysieren wird.

Nachfolgend sehen Sie die Liste der globalen Variablen und Arrays:

//--- Structure of the indicator buffers arrays
struct buffers
  {
   double            open[];   // Open prices buffer
   double            high[];   // High prices buffer
   double            low[];    // Low prices buffer
   double            close[];  // Close prices buffer
   double            icolor[]; // Buffer to determine the color of element
  };
buffers           buffer_data[SYMBOLS_COUNT];
//--- Load the class
CCanvas           canvas;
//--- Variables/arrays for copying data from OnCalculate()
int               OC_rates_total     =0; // Size of input time series
int               OC_prev_calculated =0; // Bars processed at the previous call
datetime          OC_time[];             // Opening time
double            OC_open[];             // Open prices
double            OC_high[];             // High prices
double            OC_low[];              // Low prices
double            OC_close[];            // Close prices
long              OC_tick_volume[];      // Tick volumes
long              OC_volume[];           // Real volumes
int               OC_spread[];           // Spread

//--- For the purpose of storing and checking the time of the first bar in the terminal
datetime          series_first_date[SYMBOLS_COUNT];
datetime          series_first_date_last[SYMBOLS_COUNT];
//--- Time array of the bar from which we will start drawing
datetime          limit_time[SYMBOLS_COUNT];
//--- Symbol names array
string            symbol_names[SYMBOLS_COUNT];
//--- Array of symbol inverse flags
bool              inverse[SYMBOLS_COUNT];
//--- Colors of indicator lines
color             line_colors[SYMBOLS_COUNT]={clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta};
//--- String representing the lack of the symbol
string            empty_symbol="EMPTY";
//--- Chart properties
int               window_number              =WRONG_VALUE;               // Indicator window number
int               chart_width                =0;                         // Chart width
int               chart_height               =0;                         // Chart height
int               last_chart_width           =0;                         // Last saved chart width
int               last_chart_height          =0;                         // Last saved chart height
int               chart_center_x             =0;                         // Horizontal center of chart
int               chart_center_y             =0;                         // Vertical center of chart
color             color_bar_up               =clrRed;                    // Up bar color
color             color_bar_down             =C'100,0,0';                // Down bar color
string            indicator_shortname        ="MS_PriceDivergence";      // Short name of the indicator
string            prefix                     =indicator_shortname+"_";   // Prefix for objects
//--- Name of vertical line of the price divergence starting point
string            start_price_divergence=prefix+"start_price_divergence";
//--- Canvas properties
string            canvas_name             =prefix+"canvas";          // Canvas name
color             canvas_background       =clrBlack;                 // Canvas background color
uchar             canvas_opacity          =190;                      // Opacity
int               font_size               =16;                       // Font size
string            font_name               ="Calibri";                // Font
ENUM_COLOR_FORMAT clr_format              =COLOR_FORMAT_ARGB_RAW;    // Color components should be correctly set by the user
//--- Canvas messages
string            msg_prepare_data        ="Preparing data! Please wait...";
string            msg_not_synchronized    ="Unsynchronized data! Please wait...";
string            msg_load_data           ="";
string            msg_sync_update         ="";
string            msg_last                ="";
//---
ENUM_TIMEFRAMES   timeframe_start_point  =Period();    // Timeframe for the price divergence starting point
datetime          first_period_time      =NULL;        // Time of the first specified period on chart
double            divergence_price       =0.0;         // Price of the price divergence starting point
datetime          divergence_time        =NULL;        // Time of the price divergence starting point
double            symbol_difference[SYMBOLS_COUNT];    // Difference in price relative to the current symbol
double            inverse_difference[SYMBOLS_COUNT];   // Difference that is formed when calculating inversion

Als nächstes betrachten wir Funktionen, die während der Initialisierung des Indikators verwendet werden. Im Allgemeinen gibt es keine wesentlichen Änderungen im Vergleich zur OnInit()-Funktion aus dem vorherigen Beitrag.

Fügen wir eine Prüfung hinzu, die prüft, wo der Indikator genutzt wird. Das Problem ist, dass die Entwickler des Terminals noch nicht alle Funktionen für die Steuerung der Eigenschaften der Diagramme im Strategietester implementiert haben. Deshalb beschränken wir unseren Indikator auf die Nutzung außerhalb des Strategietesters. Dazu schreiben wir eine einfache Funktion, CheckTesterMode(). Sie wird sich in der Datei Checks.mqh befinden:

//+------------------------------------------------------------------+
//| Checks if indicator is used in Strategy Tester                   |
//+------------------------------------------------------------------+
bool CheckTesterMode()
  {
//--- Report that indicator is not intended to be used in Strategy Tester
   if(MQLInfoInteger(MQL_TESTER) || 
      MQLInfoInteger(MQL_VISUAL_MODE) || 
      MQLInfoInteger(MQL_OPTIMIZATION))
     {
      Comment("Currently, the <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> indicator is not intended to be used in Strategy Tester!");
      return(false);
     }
//---
   return(true);
  }

Eine weitere neue Funktion, SetBarsColors(), ist für die Festlegung der Farben von Balken/Kerzen des aktuellen Symbols vorgesehen. Sie wird sich in der Datei Chart.mqh befinden.

//+------------------------------------------------------------------+
//| Sets colors for the current symbol bars                          |
//+------------------------------------------------------------------+
void SetBarsColors()
  {
//--- Color for the up bar, shadows and body borders of bull candlesticks
   ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up);
//--- Body color of a bull candlestick
   ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up);
//--- Line chart color and color of "Doji" Japanese candlesticks
   ChartSetInteger(0,CHART_COLOR_CHART_LINE,color_bar_up);
//--- For two-color mode
   if(TwoColor)
     {
      //--- Color for the down bar, shadows and body borders of bear candlesticks
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down);
      //--- Body color of a bear candlestick
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down);
     }
//--- If two-color mode is turned off
   else
     {
      //--- Color for the down bar, shadows and body borders of bear candlesticks
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up);
      //--- Body color of a bear candlestick
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up);
     }
  }

Während der Initialisierung müssen wir festlegen, welcher Modus im externen Parameter StartPriceDivergence ausgewählt wird. Falls Vertikale Linie ausgewählt wird, wird die globale Variable timeframe_start_point mit ihrem Standardwert, d. h. dem aktuellen Timeframe, zugewiesen. Andernfalls wird der ausgewählte Timeframe angewendet. Schreiben wir zu diesem Zweck die Funktion InitStartPointTF():

//+------------------------------------------------------------------+
//| Identifies timeframe for the price starting point mode           |
//+------------------------------------------------------------------+
void InitStartPointTF()
  {
//--- Exit if vertical line mode is selected
   if(StartPriceDivergence==VERTICAL_LINE)
      return;
//--- Otherwise define the timeframe
   switch(StartPriceDivergence)
     {
      case MONTH : timeframe_start_point=PERIOD_MN1; break;
      case WEEK  : timeframe_start_point=PERIOD_W1;  break;
      case DAY   : timeframe_start_point=PERIOD_D1;  break;
      case HOUR  : timeframe_start_point=PERIOD_H1;  break;
     }
  }

Die Funktion CheckInputParameters() sieht im Gegensatz zu der aus dem vorherigen Beitrag nun so aus:

//+------------------------------------------------------------------+
//| Checks input parameters for correctness                          |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
//--- For all other modes except the 'Vertical Line'
   if(StartPriceDivergence!=VERTICAL_LINE)
     {
      //--- If the current period is greater than or equal to the specified period of the price divergence starting point, report of it and exit
      if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point))
        {
         Print("Current timeframe should be less than one specified in the Start Price Divergence parameter!");
         Comment("Current timeframe should be less than one specified in the Start Price Divergence parameter!");
         return(false);
        }
     }
//---
   return(true);
  }

Arrays werden genauso initialisiert wie im vorherigen Beitrag. Nur die Namen und die Menge der Arrays haben sich geändert.

//+------------------------------------------------------------------+
//| First initialization of arrays                                   |
//+------------------------------------------------------------------+
void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(symbol_difference,0.0);
   ArrayInitialize(inverse_difference,0.0);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      ArrayInitialize(buffer_data[s].open,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].high,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].low,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].close,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].icolor,EMPTY_VALUE);
     }
  }
//+------------------------------------------------------------------+
//| Initializes array of symbols                                     |
//+------------------------------------------------------------------+
void InitSymbolNames()
  {
   symbol_names[0]=AddSymbolToMarketWatch(Symbol02);
   symbol_names[1]=AddSymbolToMarketWatch(Symbol03);
   symbol_names[2]=AddSymbolToMarketWatch(Symbol04);
   symbol_names[3]=AddSymbolToMarketWatch(Symbol05);
   symbol_names[4]=AddSymbolToMarketWatch(Symbol06);
  }
//+------------------------------------------------------------------+
//| Initializes array of inversions                                  |
//+------------------------------------------------------------------+
void InitInverse()
  {
   inverse[0]=Inverse02;
   inverse[1]=Inverse03;
   inverse[2]=Inverse04;
   inverse[3]=Inverse05;
   inverse[4]=Inverse06;
  }

An der Funktion SetIndicatorProperties() wurden wesentliche Änderungen vorgenommen. Faktisch ist es eine ganz neue Funktion. Jetzt werden die entsprechenden Eigenschaften je nachdem, welcher Modus der Datendarstellung ausgewählt ist, während der Initialisierung festgelegt.

//+------------------------------------------------------------------+
//| Sets indicator properties                                        |
//+------------------------------------------------------------------+
void SetIndicatorProperties()
  {
//--- Set the short name
   IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname);
//--- Set the number of decimal digits
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//---  In the 'Line' mode we need only one buffers that displays the open price
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA);
     }
//--- In other modes we use all prices for drawing 
//    bars/candlesticks and additional buffer for the two-color mode
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         static int buffer_number=0;
         SetIndexBuffer(buffer_number,buffer_data[s].open,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].high,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].low,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].close,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].icolor,INDICATOR_COLOR_INDEX);
         buffer_number++;
        }
     }
//--- Set labels for the current timeframe
//    In the 'Line' mode only opening price is used
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close");
     }
//--- In other modes all prices of bars/candlesticks
//    ";" is used as a separator
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         PlotIndexSetString(s,PLOT_LABEL,
                            symbol_names[s]+",Open;"+
                            symbol_names[s]+",High;"+
                            symbol_names[s]+",Low;"+
                            symbol_names[s]+",Close");
        }
     }
//--- Set the type of lines for indicator buffers
//--- Line
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
//--- Bars
   if(DrawType==BARS)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS);
//--- Candlesticks
   if(DrawType==CANDLES)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES);

//--- Set the type of lines for data of current symbol
//--- Line
   if(DrawType==LINE)
      ChartSetInteger(0,CHART_MODE,CHART_LINE);
//--- Bars
   if(DrawType==BARS)
      ChartSetInteger(0,CHART_MODE,CHART_BARS);
//--- Candlesticks
   if(DrawType==CANDLES)
      ChartSetInteger(0,CHART_MODE,CHART_CANDLES);

//--- Set the line width
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1);
//--- Set the line color for the 'Line' mode
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
//--- Display data in Data Window only for existing symbols
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      if(symbol_names[s]!=empty_symbol)
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,true);
      else
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,false);
     }
//--- Empty value for plotting where nothing will be drawn
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

Und zu guter Letzt wird eine weitere neue Funktion, SetDivergenceLine(), in OnInit() verwendet. Sie setzt die vertikale grüne Linie zur Steuerung des Ausgangspunkts des Preisunterschieds im Modus Vertikale Linie.

//+------------------------------------------------------------------+
//| Sets vertical line for price divergence starting point           |
//+------------------------------------------------------------------+
void SetDivergenceLine()
  {
//--- If there is no vertical line yet, set it
   if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(0,start_price_divergence)<0)
      //--- Place a vertical line on the true bar
      CreateVerticalLine(0,0,TimeCurrent()+PeriodSeconds(),start_price_divergence,
                         2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n");
//--- For all other modes except the 'Vertical Line'
   if(StartPriceDivergence!=VERTICAL_LINE)
      DeleteObjectByName(start_price_divergence);
  }

Nachfolgend sehen Sie die Darstellung aller oben beschriebenen Erwägungen innerhalb der Funktion OnInit(). Wenn alles in separate Funktionen und Dateien aufgeteilt ist, wird es sehr einfach, den Code des Programms zu lesen.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Check if indicator is currently being used in Strategy Tester
   if(!CheckTesterMode())
      return(INIT_FAILED);
//--- Set the color for bars/candlesticks
   SetBarsColors();
//--- Define the timeframe for the price divergence starting point
   InitStartPointTF();
//--- Check input parameters for correctness
   if(!CheckInputParameters())
      return(INIT_PARAMETERS_INCORRECT);
//--- Set the timer at 1-second intervals
   EventSetMillisecondTimer(1000);
//--- Set the font to be displayed on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Initialization of arrays
   InitArrays();
//--- Initialize the array of symbols 
   InitSymbolNames();
//--- Initialize the array of inversions
   InitInverse();
//--- Set indicator properties
   SetIndicatorProperties();
//--- Set vertical line of the price divergence start
   SetDivergenceLine();
//--- Clear the comment
   Comment("");
//--- Refresh the chart
   ChartRedraw();
//--- Initialization completed successfully
   return(INIT_SUCCEEDED);
  }

In der Funktion OnCalculate() ist der Code des Programms so gut wie unverändert geblieben. Im vorherigen Beitrag füllte das Programm nach der Überprüfung der Verfügbarkeit der Daten zuerst Hilfs-Arrays aus und befüllte erst dann die Indikatorpuffer mit den vorbereiteten Daten. Diesmal versuchen wir, alles in einer einzigen Schleife umzusetzen.

Ich habe die Funktionen zum Validieren und Laden von Daten strenger gestaltet. Nun durchläuft jeder Wert, den Sie erhalten möchten, eine festgelegte Anzahl von Versuchen. Wenn ein Wert erhalten wird, wird die Schleife angehalten. Und da wir nun Modi haben, in denen wir den Ausgangspunkt eines Zeitraums (Monat, Woche, Tag, Stunde) festlegen müssen, erhalten wir die Startzeit des Zeitraums über den höheren Timeframe. Deshalb habe ich eine zusätzliche Funktion erstellt, die LoadAndFormData() ähnlich ist und mit LoadAndFormDataHighTF() ähnlich benannt ist. Ihr Code ist dem des Originals sehr ähnlich, weshalb ich ihn hier nicht aufführen werde.

Die Überprüfung der Verfügbarkeit von Daten für den aktuellen und höhere Timeframes wurde in der einzelnen Funktion CheckAvailableData() umgesetzt:

//+------------------------------------------------------------------+
//| Checks the amount of available data for all symbols              |
//+------------------------------------------------------------------+
bool CheckAvailableData()
  {
   int attempts=100;
   
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If this symbol is available
      if(symbol_names[s]!=empty_symbol)
        {
datetime time[];                    // Array for checking the number of bars
   int      total_period_bars   =0;    // Number of bars of the current period
   datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal
         //--- Get the first date of the current time frame data in the terminal
         terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE);
         //--- Get the number of available bars from the date specified
         total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent());
         //--- Check the readiness of bar data
         for(int i=0; i<attempts; i++)
           {
            //--- Copy the specified amount of data
            if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time))
              {
               //--- If the required amount has been copied, terminate the loop
               if(ArraySize(time)>=total_period_bars)
                  break;
              }
           }
         //--- If the amount of data copied is not sufficient, one more attempt is required
         if(ArraySize(time)==0 || ArraySize(time)<total_period_bars)
           {
            msg_last=msg_prepare_data;
            ShowCanvasMessage(msg_prepare_data);
            OC_prev_calculated=0;
            return(false);
           }
        }
     }
//--- Exit if current mode is vertical line of the price divergence starting point
   if(StartPriceDivergence==VERTICAL_LINE)
      return(true);
   else
     {
      datetime time[];                    // Array for checking the number of bars
      int      total_period_bars   =0;    // Number of bars of the current period
      datetime terminal_first_date =NULL; // First date of the current time frame data available in the terminal
      //--- Get the first date of the current time frame data in the terminal
      for(int i=0; i<attempts; i++)
         if((terminal_first_date=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>0)
            break;
      //--- Get the number of available bars from the date specified
      for(int i=0; i<attempts; i++)
         if((total_period_bars=(int)SeriesInfoInteger(Symbol(),timeframe_start_point,SERIES_BARS_COUNT))>0)
            break;
      //--- Check the readiness of bar data
      for(int i=0; i<attempts; i++)
         //--- Copy the specified amount of data
         if(CopyTime(Symbol(),timeframe_start_point,
            terminal_first_date+PeriodSeconds(timeframe_start_point),TimeCurrent(),time)>0)
            break;
      //--- If the amount of data copied is not sufficient, one more attempt is required
      if(ArraySize(time)<=0 || total_period_bars<=0)
        {
         msg_last=msg_prepare_data;
         ShowCanvasMessage(msg_prepare_data);
         OC_prev_calculated=0;
         return(false);
        }
     }
//---
   return(true);
  }

Die Funktion FillIndicatorBuffers() wurde für die vorliegende Aufgabe viel komplexer. Der Grund dafür ist, dass es nun mehrere Modi gibt, von denen jeder seine eigenen Aktionen benötigt. Tatsächlich lässt sich alles in vier Schritte aufteilen.

  • Abrufen der Daten für das angegebene Symbol.
  • Abrufen der Daten für den höheren Timeframe und Bestimmen der Zeit und des Preisniveaus, bei denen sich die Preise aller Symbole treffen.
  • Berechnen von Werten und Ausfüllen des Indikatorpuffers.
  • Überprüfung der berechneten Werte.

Der Code der Funktion enthält detaillierte Kommentare für Ihre Lernzwecke:

//+------------------------------------------------------------------+
//| Fills indicator buffers                                          |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(int i,int s,datetime const &time[])
  {
   MqlRates    rates[];             // Data structure
   double      period_open[];       // Opening price for bar at the price divergence starting point
   datetime    period_time[];       // Time of the price divergence starting point
   int         attempts=100;        // Number of copying attempts
   datetime    high_tf_time=NULL;   // Time of higher timeframe's bar

//--- Exit if we are out of "true" bars zone
   if(time[i]<limit_time[s])
      return;
//--- Reset the last error
   ResetLastError();
//--- Get data of current bar for the specified symbol
   for(int j=0; j<attempts; j++)
      if(CopyRates(symbol_names[s],Period(),time[i],1,rates)==1)
        { ResetLastError(); break; }
//--- Exit if failed to get data
   if(ArraySize(rates)<1 || GetLastError()!=0)
      return;
//--- If the current time is before the first timeframe's time or
//    bar time is not equal to the bar time of the current symbol or
//    empty values are fetched
   if(rates[0].time==NULL || 
      time[i]!=rates[0].time || 
      time[i]<first_period_time || 
      rates[0].low==EMPTY_VALUE || 
      rates[0].open==EMPTY_VALUE ||
      rates[0].high==EMPTY_VALUE ||
      rates[0].close==EMPTY_VALUE)
     {
      //--- Write empty value
      if(DrawType!=LINE)
        {
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
        }
      buffer_data[s].close[i]=EMPTY_VALUE;
      return;
     }
//--- If current mode is vertical line of the price divergence starting point
   if(StartPriceDivergence==VERTICAL_LINE)
     {
      //--- Get the time of the line
      divergence_time=(datetime)ObjectGetInteger(0,start_price_divergence,OBJPROP_TIME);
      //--- Get the time of the first bar
      first_period_time=time[0];
     }
//--- For all other modes, we will keep track the beginning of period
   else
     {
      //--- If we are here for the first time, store data of the first bar of higher timeframe
      if(divergence_time==NULL)
        {
         ResetLastError();
         //--- Get opening time of the first bar of higher timeframe
         for(int j=0; j<attempts; j++)
            if(CopyTime(Symbol(),timeframe_start_point,time[0]+PeriodSeconds(timeframe_start_point),1,period_time)==1)
              { ResetLastError(); break; }
         //--- Exit if failed to get price/time
         if(ArraySize(period_time)<1 || GetLastError()!=0)
            return;
         //--- Otherwise store time of the first bar of higher timeframe
         else
            first_period_time=period_time[0];
        }
      //--- If current bar's time on the current timeframe is before the first bar's time on higher timeframe
      if(time[i]<first_period_time)
         high_tf_time=first_period_time;
      //--- Otherwise we will receive data of the last bar of the higher timeframe with respect to the current bar on the current timeframe
      else
         high_tf_time=time[i];
      //--- Reset the last error
      ResetLastError();
      //--- Get the opening price of the first bar of the higher timeframe
      for(int j=0; j<attempts; j++)
         if(CopyOpen(Symbol(),timeframe_start_point,high_tf_time,1,period_open)==1)
           { ResetLastError(); break; }
      //--- Get opening time of the first bar of higher timeframe
      for(int j=0; j<attempts; j++)
         if(CopyTime(Symbol(),timeframe_start_point,high_tf_time,1,period_time)==1)
           { ResetLastError(); break; }
      //--- Exit if failed to get price/time
      if(ArraySize(period_open)<1 || ArraySize(period_time)<1 || GetLastError()!=0)
         return;
      //--- If the current timeframe's time is before the first period's time or
      //    time of specified period is not equal to the one in memory
      if(time[i]<first_period_time || divergence_time!=period_time[0])
        {
         symbol_difference[s]  =0.0; // Zero out difference in symbol prices
         inverse_difference[s] =0.0; // Zero our difference of inversion
         //--- Store time of the price divergence starting point
         divergence_time=period_time[0];
         //--- Store price of the price divergence starting point
         divergence_price=period_open[0];
         //--- Set vertical line in the beginning of the price divergence start
         CreateVerticalLine(0,0,period_time[0],start_price_divergence+"_"+TimeToString(divergence_time),
                            2,STYLE_SOLID,clrWhite,false,false,true,TimeToString(divergence_time),"\n");
        }
     }
//--- If current mode is 'Vertical Line' and bar's time is less than line's time
   if(StartPriceDivergence==VERTICAL_LINE && time[i]<divergence_time)
     {
      //--- Keep zero values of difference
      symbol_difference[s]  =0.0;
      inverse_difference[s] =0.0;
      //--- For the 'Line' drawing mode only opening price is used
      if(DrawType==LINE)
         buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
      //--- For all other modes all prices are used
      else
        {
         buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
         buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
         buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
         buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
         //--- Set color for the current element of indicator buffer
         SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
        }
     }
//--- For all other modes
   else
     {
      //--- If inversion of symbol data is required
      if(inverse[s])
        {
         //--- If new period has started, recalculate variables
         if(symbol_difference[s]==0.0)
           {
            //--- For the 'Vertical Line' mode
            if(StartPriceDivergence==VERTICAL_LINE)
              {
               //--- Calculate the difference
               symbol_difference[s]  =rates[0].open-OC_open[i];
               inverse_difference[s] =OC_open[i]-(-OC_open[i]);
              }
            //--- For all other modes
            else
              {
               //--- Calculate the difference
               symbol_difference[s]  =rates[0].open-divergence_price;
               inverse_difference[s] =divergence_price-(-divergence_price);
              }
           }
         //--- In the 'Line' mode only opening price is used
         if(DrawType==LINE)
            buffer_data[s].close[i]=-(rates[0].close-symbol_difference[s])+inverse_difference[s];
         //--- For all other modes all prices are used
         else
           {
            buffer_data[s].low[i]   =-(rates[0].low-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].open[i]  =-(rates[0].open-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].high[i]  =-(rates[0].high-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].close[i] =-(rates[0].close-symbol_difference[s])+inverse_difference[s];
            //--- Set color for the current element of indicator buffer
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
      //--- If inversion is not used, then we need to calculate only the difference between symbol prices at the beginning of period
      else
        {
         //--- If new period has started
         if(symbol_difference[s]==0.0)
           {
            //--- For the 'Vertical Line' mode
            if(StartPriceDivergence==VERTICAL_LINE)
               symbol_difference[s]=rates[0].open-OC_open[i];
            //--- For all other modes
            else
               symbol_difference[s]=rates[0].open-divergence_price;
           }
         //--- For the 'Line' drawing mode only opening price is used
         if(DrawType==LINE)
            buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
         //--- For all other modes all prices are used
         else
           {
            buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
            buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
            buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
            buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
            //--- Set color for the current element of indicator buffer
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
     }
//--- Verification of the calculated values
//    In the 'Line' mode only opening price is used
   if(DrawType==LINE)
     {
      //--- If the current time is before the first timeframe's time or
      //    bar time is not equal to the bar time, write empty value
      if(time[i]!=rates[0].time || time[i]<first_period_time)
         buffer_data[s].close[i]=EMPTY_VALUE;
     }
//--- For all other modes all prices are used
   else
     {
      //--- If the current time is before the first timeframe's time or
      //    bar time is not equal to the bar time of the current symbol or
      //    empty values are fetched
      if(rates[0].time==NULL || 
         time[i]!=rates[0].time || 
         time[i]<first_period_time || 
         rates[0].low==EMPTY_VALUE || 
         rates[0].open==EMPTY_VALUE ||
         rates[0].high==EMPTY_VALUE ||
         rates[0].close==EMPTY_VALUE)
        {
         //--- Write empty value
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
         buffer_data[s].close[i] =EMPTY_VALUE;
        }
     }
  }

Beim Studieren der oben aufgeführten Funktion sollten Sie eine weitere benutzerdefinierte Funktion, SetBufferColorIndex(), bemerken. Diese Funktion legt die Farbe im Farbpuffer des Indikators fest.

//+------------------------------------------------------------------+
//| Sets the color for buffer element by condition                   |
//+------------------------------------------------------------------+
void SetBufferColorIndex(int i,int symbol_number,double close,double open)
  {
//--- For two-color mode, check condition
   if(TwoColor)
     {
      //--- If the closing price is more than the opening price, this is up bar, so we use the first color
      if(close>open)
         buffer_data[symbol_number].icolor[i]=0;
      //--- otherwise it is down bar, so we use the second color
      else
         buffer_data[symbol_number].icolor[i]=1;
     }
//--- For one-color mode we use the first color for all bars/candlesticks
   else
      buffer_data[symbol_number].icolor[i]=0;
  }

Sobald die Indikatorpuffer ausgefüllt sind, müssen wir das Maximum und Minimum von allen Werten bestimmen, die aktuell im Diagrammfenster zu sehen sind. MQL5 bietet die Möglichkeit, die Nummer des ersten sichtbaren Balkens und die Menge der sichtbaren Balken abzurufen. In CorrectChartMaxMin(), einer weiteren benutzerdefinierten Funktion, profitieren wir von diesen Features. Der Codefluss in dieser Funktion lässt sich in mehrere Schritte einteilen:

  • Bestimmen der Nummer des ersten und letzten sichtbaren Balkens.
  • Bestimmen des Maximums und Minimums der sichtbaren Balken für das aktuelle Symbol.
  • Bestimmen des Maximums und Minimums unter allen Arrays von Symbolen.
  • Festlegen des Maximums und Minimums in den Diagrammeigenschaften.

Nachfolgend sehen sie den Code der Funktion CorrectChartMaxMin() in der Datei Chart.mqh.

//+------------------------------------------------------------------+
//| Corrects chart's high/low with respect to all buffers            |
//+------------------------------------------------------------------+
void CorrectChartMaxMin()
  {
   double low[];                  // Array of lows
   double high[];                 // Array of highs
   int    attempts          =10;  // Number of attempts
   int    array_size        =0;   // Array size for drawing
   int    visible_bars      =0;   // Number of visible bars
   int    first_visible_bar =0;   // Number of the first visible bar
   int    last_visible_bar  =0;   // Number of the last visible bar
   double max_price         =0.0; // Highest price
   double min_price         =0.0; // Lowest price
   double offset_max_min    =0.0; // Offset from chart's high/low
//--- Reset the last error
   ResetLastError();
//--- Number of visible bars
   visible_bars=(int)ChartGetInteger(0,CHART_VISIBLE_BARS);
//--- Number of the first visible bar
   first_visible_bar=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR);
//--- Number of the last visible bar
   last_visible_bar=first_visible_bar-visible_bars;
//--- Exit in case of error
   if(GetLastError()!=0)
      return;
//--- Fix incorrect value
   if(last_visible_bar<0)
      last_visible_bar=0;
//--- Get the current symbol high/low in visible area of chart
   for(int i=0; i<attempts; i++)
      if(CopyHigh(Symbol(),Period(),last_visible_bar,visible_bars,high)==visible_bars)
         break;
   for(int i=0; i<attempts; i++)
      if(CopyLow(Symbol(),Period(),last_visible_bar,visible_bars,low)==visible_bars)
         break;
//--- Exit if failed to get data
   if(ArraySize(high)<=0 || ArraySize(low)<=0)
      return;
//--- If succeeded to get data, identify high and low in the current symbol arrays
   else
     {
      min_price=low[ArrayMinimum(low)];
      max_price=high[ArrayMaximum(high)];
     }
//--- Get high and low prices in all price arrays
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If current symbol is not present, go to the next one
      if(symbol_names[s]==empty_symbol)
         continue;
      //---
      datetime time[];         // Time array
      int      bars_count=0; // Number of bars for calculation
      //--- Set zero size for arrays
      ArrayResize(high,0);
      ArrayResize(low,0);
      //--- Get the time of the first bar visible on chart
      for(int i=0; i<attempts; i++)
         if(CopyTime(Symbol(),Period(),last_visible_bar,visible_bars,time)==visible_bars)
            break;
      //--- Exit if the amount of data is less than number of visible bars on chart
      if(ArraySize(time)<visible_bars)
         return;
      //--- If time of the first "true" bar is greater than
      //    time of the first visible bar on the chart, then
      //    get available number of bars of the current symbol in loop
      if(limit_time[s]>time[0])
        {
         //--- Get the array size
         array_size=ArraySize(time);
         //--- Get the number of bars from the first "true" one
         if((bars_count=Bars(Symbol(),Period(),limit_time[s],time[array_size-1]))<=0)
            return;
        }
      //--- Else get number of visible bars on chart
      else
         bars_count=visible_bars;
      //--- Index elements in indicator buffers as timeseries
      ArraySetAsSeries(low,true);
      ArraySetAsSeries(high,true);
      //--- Copy data from the indicator buffer
      //    All modes except 'Line'
      if(DrawType!=LINE)
        {
         ArrayCopy(low,buffer_data[s].low);
         ArrayCopy(high,buffer_data[s].high);
        }
      //--- For the 'Line' mode
      else
        {
         ArrayCopy(low,buffer_data[s].close);
         ArrayCopy(high,buffer_data[s].close);
        }
      //--- Get the array size
      array_size=ArraySize(high);
      //--- Fill empty values,
      //    so they are not considered when calculating high/low
      for(int i=0; i<array_size; i++)
        {
         if(high[i]==EMPTY_VALUE)
            high[i]=max_price;
         if(low[i]==EMPTY_VALUE)
            low[i]=min_price;
        }
      //--- Get high/low with respect to inversion
      if(inverse[s])
        {
         //--- If no errors occur, store values
         if(ArrayMaximum(high,last_visible_bar,bars_count)>=0 && 
            ArrayMinimum(low,last_visible_bar,bars_count)>=0)
           {
            max_price=fmax(max_price,low[ArrayMaximum(low,last_visible_bar,bars_count)]);
            min_price=fmin(min_price,high[ArrayMinimum(high,last_visible_bar,bars_count)]);
           }
        }
      else
        {
         //--- If no errors occur, store values
         if(ArrayMinimum(low,last_visible_bar,bars_count)>=0 && 
            ArrayMaximum(high,last_visible_bar,bars_count)>=0)
           {
            min_price=fmin(min_price,low[ArrayMinimum(low,last_visible_bar,bars_count)]);
            max_price=fmax(max_price,high[ArrayMaximum(high,last_visible_bar,bars_count)]);
           }
        }
     }
//--- Calculate offset (3%) form chart's top and bottom
   offset_max_min=((max_price-min_price)*3)/100;
//--- Turn on the fixed chart scale mode.
   ChartSetInteger(0,CHART_SCALEFIX,true);
//--- Set high/low
   ChartSetDouble(0,CHART_FIXED_MAX,max_price+offset_max_min);
   ChartSetDouble(0,CHART_FIXED_MIN,min_price-offset_max_min);
//--- Refresh the chart
   ChartRedraw();
  }

Die oben beschriebene Funktion wird beim Verarbeiten des Ereignisses des Ziehens der vertikalen Linie verwendet (und natürlich beim Berechnen der Indikatorwerte in OnCalculate):

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Event of dragging a graphical object
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- If current mode is vertical line for the price divergence starting point, then update indicator buffers
      if(StartPriceDivergence==VERTICAL_LINE)
         OnCalculate(OC_rates_total,
                     0,
                     OC_time,
                     OC_open,
                     OC_high,
                     OC_low,
                     OC_close,
                     OC_tick_volume,
                     OC_volume,
                     OC_spread);
     }
//--- Event of resizing the chart or modifying the chart properties using the properties dialog window.
   if(id==CHARTEVENT_CHART_CHANGE)
      //--- Correct the maximum and minimum of chart with respect to the indicator buffers' values
      CorrectChartMaxMin();
  }

Alle Funktionen sind bereit. Sie können den vollständig kommentierten Code im Anhang dieses Beitrags im Detail betrachten.

Lassen Sie uns vorführen, was wir erhalten haben. Standardmäßig werden die Symbole GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF in den externen Parametern festgelegt. Im nachfolgenden Screenshot sehen Sie das Wochendiagramm für EURUSD im Modus Vertikale Linie mit deaktivierter Umkehrung:

Wöchentlicher Timeframe im Modus "Vertikale Linie"

Abb. 1 – Wöchentlicher Timeframe im Modus "Vertikale Linie"

Im nachfolgenden Screenshot sehen Sie den M30-Timeframe im Modus Tag, doch diesmal ist die Umkehrung für Symbole mit USD als Basiswährung aktiviert. In unserem Fall haben wir USDCAD (hellblaue Kerzen) und USDCHF (violette Kerzen).

M30-Timeframe im Modus "Tag"

Abb. 2 – M30-Timeframe im Modus "Tag"


Fazit

Ich denke, wir haben ein ziemlich interessantes und informationsreiches Werkzeug für die mehrwährungsfähige Analyse von Preisunterschieden geschaffen. Dieser Indikator lässt sich unendlich ausbauen und verbessern.

Vielen Dank für Ihre Zeit!

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

Beigefügte Dateien |
Das MQL5-Kochbuch – Mehrwährungsfähiger Expert Advisor und die Arbeit mit Pending Orders in MQL5 Das MQL5-Kochbuch – Mehrwährungsfähiger Expert Advisor und die Arbeit mit Pending Orders in MQL5
Diesmal werden wir einen mehrwährungsfähigen Expert Advisor mit einem Handelsalgorithmus erstellen, der auf der Arbeit mit den Pending Orders Buy Stop und Sell Stop basiert. Folgende Themen werden in diesem Beitrag erörtert: der Handel in einem festgelegten Zeitbereich, Platzieren/Modifizieren/Löschen von Pending Orders, die Prüfung, ob die letzte Position bei Take Profit oder Stop Loss geschlossen wurde, und die Kontrolle der Historie der Abschlüsse für jedes Symbol.
Grundlagen der Programmierung in MQL5: Listen Grundlagen der Programmierung in MQL5: Listen
Die neue Version der Programmiersprache für die Entwicklung von Handelsstrategien, MQL [MQL5], liefert im Vergleich zur Vorgängerversion [MQL4] leistungsstärkere und effektivere Features. Der Vorteil besteht im Wesentlichen aus den Merkmalen der objektorientierten Programmierung. In diesem Beitrag wird die Möglichkeit betrachtet, komplexe benutzerdefinierte Datentypen wie Knoten und Listen zu verwenden. Außerdem liefert der Beitrag ein Anwendungsbeispiel für die Verwendung von Listen in der praktischen Programmierung in MQL5.
Indikator für Renko-Diagramme Indikator für Renko-Diagramme
Dieser Beitrag beschreibt ein Beispiel für Renko-Diagramme und dessen Umsetzung als Indikator in MQL5. Dieser Indikator unterscheidet sich durch Modifikationen von einem herkömmlichen Diagramm. Er kann sowohl im Indikatorfenster als auch im Hauptdiagramm konstruiert werden. Außerdem gibt es noch den ZigZag-Indikator. Sie können einige Beispiele für die Umsetzung des Diagramms finden.
Universeller Expert Advisor: Traden mit Gruppen von Strategien und deren Verwaltung (Part 4) Universeller Expert Advisor: Traden mit Gruppen von Strategien und deren Verwaltung (Part 4)
In dem letzten Abschnitt der Serie über die CStrategy Trading Engine, werden wir die parallele Ausführung von verschiedenen Handels-Algorithmen betrachten, das Laden von Strategien über ein XML-File kennenlernen und wir werden ein einfaches Bedienfeld erstellen, mit welchem es möglich ist, die Strategie(n) auszuwählen und auch die Handelsmodi.