MetaTrader 5 herunterladen

Indikator für das Zeichnen von Point-and-Figure-Charts

27 Juni 2016, 14:59
Dmitriy Zabudskiy
0
447

Einleitung

Es gibt viele verschiedene Charttypen, die Informationen zur aktuellen Marktsituation anzeigen. Viele von Ihnen - wie beispielsweise Point-&-Figure-Charts - sind die Hinterlassenschaften einer weit zurückliegenden Vergangenheit.

Dieser Charttyp ist bereits seit dem Ende des 19. Jahrhunderts bekannt. Er wurde erstmals von Charles Dow in dessen Leitartikel (Wall Street Journal, 20. Juli 1901) erwähnt, der Ihn als „Book“-Methode etikettierte. Und obwohl Dow die Existenz dieser „Book“-Methode bis zum Jahr 1886 zurückverfolgte, war er dennoch offiziell der Erste, der sie niederschrieb.

Trotz des Umstandes, dass Dow diese Methode lediglich in einem Leitartikel beschrieb, finden sich heute mannigfaltige Bücher und Werke, die auf diese Methode detaillierter eingehen. Eines der Bücher, das ich Trading-Neulingen empfehlen würde, ist das Werk von Thomas J. Dorsey, „Point and Figure Charting“: The Essential Application for Forecasting and Tracking Market Prices".

 

Beschreibung

Ein Point-&-Figure-Chart ist ein Set vertikaler Säulen: Ein X repräsentiert steigende und ein O steht für fallende Kurse. Die Besonderheit: Es wird wird anhand der Kursentwicklung und nicht der Zeit gezeichnet. Wenn folglich ein Wert der Diagrammdaten (Zeit) entfernt wird, so erhalten wir Charts mit Trendlinien, die in einem Winkel von 45 Grad gezeichnet werden.

Point-&-Figure-Charts werden unter Verwendung zweier vordefinierter Werte gezeichnet:

  • Die Boxgröße (Box Size) ist die „Menge“ an Kursbewegung, die benötigt wird, um ein X oder ein O hinzuzufügen (Ursprünglich drückte dieser Wert die Menge an Dollar pro Aktie aus. Im Laufe der Zeit wurde dieser Indikator allerdings zu einer Aneinanderreihung von Punkten).
  • Der Auflösungsbetrag (Reversal Amount) ist die „Menge“ an Preisrückkehr in Box-Einheiten, die es bedarf, um Säulen von X zu O oder andersherum zu ändern (Beispiel: Ein Auflösungsbetrag von 3 und eine Boxgröße von 10 ergibt 30 Punkte).

Wir wählen also einen Startpunkt aus und setzen ein X/O für einen steigenden/fallenden Kurs, vorausgesetzt, dass der Kurs sich entsprechend eines Wertes geändert hat, der gleich der Boxgröße multipliziert mit dem Auflösungsbetrag ist. Ferner, falls sich der Kurs weiter in die selbe Richtung bewegt (Änderung der Boxgröße), fügen wir ein X an die Spitze der X-Säule (Anstieg) bzw. ein O an den Boden der O-Säule (Abfallen) hinzu. Falls sich der Kurs in die entgegengesetzte Richtung mit einem Wert bewegt hat, der dem Produkt von Boxgröße und Auflösungsbetrag entspricht, schreiben wir ein X für einen Kursanstieg bzw. ein O für einen Kursabfall, wodurch wir also eine neue X-/O-Säule anlegen.

Aus Gründen der Einfachheit werden Point-&-Figure-Charts normalerweise auf kariertem Papier gezeichnet. Zum besseren Verständnis wollen wir uns an dieser Stelle ein kleines Beispiel zum Zeichnen von Point-&-Figure-Charts ansehen. Nehmen wir folgende Daten an:

Datum Höchster Preis Niedrigster Preis
03.07.2013 12:00 - 03.07.2013 20:00 1,3117 1,2989
03.07.2013 20:00 - 03.08.2013 04:00 1,3118 1,3093
03.08.2013 04:00 - 03.08.2013 12:00 1,3101 1,3080
03.08.2013 12:00 - 03.08.2013 20:00 1,3134 1,2955

Wir werden den Point-&-Figure-Chart zeichnen, falls eine Boxgröße von 10 und ein Auflösungsbetrags von 3 vorliegt:

  • Anfangs sehen wir einen Kursanstieg von 128 Punkten, von 1.2989 auf 1.3117. Wir zeichnen also 12 Mal ein X.
  • Dann fällt der Kurs von 1.3118 auf 1.3093 um 25 Punkte. Dies genügt nicht für einen Umschwung. Also lassen wir alles so, wie es ist.
  • Ferner können wir beobachten, dass der Kurs weiterhin im Fallen in Richtung 1.3080 begriffen ist. Angesichts des alten Werts 1.3118 gibt es eine Änderung von 38 Punkten: Wir können also eine neue Säule beginnen, indem wir zweimal ein O hinzufügen (obwohl die Kursbewegung den Wert von drei Boxen übertroffen hat, fügen wir nur zwei Os hinzu, da die nachfolgende O-Säule immer eine Box tiefer beginnt).
  • Dann steigt der Preis von 1.3080 auf 1.3134 um 54 Punkte, um gleich darauf wieder um 179 Punkte auf 1.2955 zu fallen. Die nächste Säule weist also viermal X auf, gefolgt von einer O-Säule, die 16 Os besitzt.

Sehen wir uns die untere Darstellung an:

Abb. 1 Japanischer Kerzenchart (links) und Point-&-Figure-Chart (rechts)

Abb. 1 Japanischer Kerzenchart (links) und Point-&-Figure-Chart (rechts)

Das obere Beispiel eines Point-&-Figure-Charts ist ziemlich grob und soll lediglich dazu dienen, das Konzept zu verstehen.

 

Prinzipien beim Zeichnen lassen eines Charts (charting)

Es gibt mehrere verschiedene Point-&-Figure-Chart-Techniken, von denen eine bereits weiter oben beschrieben wurde. Diese Chart-Techniken unterscheiden sich in den Daten, die sie benutzen. Wir können zum Beispiel - ohne Intraday-Bewegungen zu beachten - tägliche Daten benutzen, also eine grobe grafische Darstellung erhalten. Oder wir machen es andersherum, indem wir Intraday-Kursbewegungen mit einbeziehen, wodurch wir eine entsprechend detailliertere Darstellung erhalten.

Um einen glatteren und akurateren Point-&-Figure-Chart zu erhalten, wurde entschieden, Minutendaten einer Kursbewegung für Kalkulationen und zum Zeichnen eines Charts zu verwenden, da Kursbewegungen über eine Minute hinaus nicht signifikant sind (normalerweise bis 6 Punkte, wobei 2 bis 3 Punkte die Norm zu sein scheinen). Wir werden also die Eröffnungskursdaten auf jedem Minutenbalken verwenden.

Das Prinzip des Zeichnens ist dabei relativ simpel:

  • Wir wählen einen Startpunkt, zum Beispiel den Eröffnungskurs des ersten Minutenbalkens.
  • Dann, falls die Kursbewegung eine Distanz zurücklegt, die dem Produkts aus Boxgröße und Auflösungsbetrag entspricht oder dieses übersteigt, zeichnen wir die entsprechenden Symbole (ein O für eine Abwärts- und ein X für eine Aufwärtsbewegung). Die Daten des letzten Symbolkurses werden für zukünftige Chartzeichnungen gespeichert.
  • Für den Fall, dass sich der Preis um die Boxgröße in die selbe Richtung bewegen sollte, wird ein entsprechendes Symbol gezeichnet.
  • Außerdem werden im Falle einer Preisumkehr die Kalkulationen anhand des Kurses des letzten Symbols und nicht anhand des höchsten Preises des Paares durchgeführt. Mit anderen Worten: Wenn die Kursbewegung nicht mehr als 50% der Boxgröße beträgt, wird sie ganz einfach ignoriert.

Lassen Sie uns nun den Stil des Point-&-Figure-Charts definieren: Die MQL5-Sprache unterstützt sieben Stile zum Zeichnen von Indikatoren: Linie, Sektion (Segment), Histogramm, Pfeil (Symbol), ausgefültes Gebiet (ausgefülter Kanal), Balken und japanische Kerzencharts.

Pfeile (Symbole) wären für eine visuelle Präsentation geradezu perfekt. Allerdings bedarf dieser Stil einer schwankenden Anzahl an Indikatorpuffern (was leider nicht von MQL5 unterstützt wird) oder aber einer enormen Anzahl selbiger, da das Zeichnen eines einzigen X oder Os einen separaten Indikatorpuffer benötigt. Das heißt, sollten Sie sich für diesen Stil entscheiden, so sollten Sie die Volatilität definieren sowie über ausreichend Speicher verfügen.

Wir haben uns also für japanische Kerzencharts als Chart-Stil entschieden. Genauer gesagt: Gefäbrte japanische Kerzencharts. Unterschiedliche Farben sollen dabei X- und O-Säulen farblich unterscheidbar machen. Der Indikator benötigt also lediglich 5 Puffer - das ist ressourcenschonend.

Säulen werden mittels horizontaler Linien in Boxgrößen unterteilt. Die Resultate, die wir erhalten, sind einigermaßen beeindruckend:

Abb. 2 Anlegen eines Charts unter Verwendung des Indikators EURUSD (täglicher Zeitrahmen).

Abb. 2 Anlegen eines Charts unter Verwendung des Indikators EURUSD (täglicher Zeitrahmen).

 

Algorithmus des Indikators

Zuerst müssen wir die Eingabeparameter des Indikators bestimmen. Da der Point-&-Figure-Chart nicht die Zeit berücksichtigt und wir Daten von den Minutenbalken zum Zeichnen verwenden, müssen wir die Menge an zu verarbeitenden Daten spezifizieren, um nicht mehr Systemressourcen als notwendig zu beanspruchen. Es gibt außerdem keinen Grund, einen Point-&-Figure-Chart zu zeichnen, der auf der gesamten Historie basiert. Wir führen also an dieser Stelle den ersten Parameter ein - History. Er wird die Anzahl an Minutenbalken berücksichtigen.

Ferner müssen wir noch die Boxgröße sowie den Auflösungsbetrag festsetzen. Zu diesem Zwecke werden wir die Variablen Cell und CellForChange einführen. Außerdem legen wir noch die Farbparameter ColorUp (X) und ColorDown (O) an. Der letzte, noch ausstehende Parameter ist schließlich die Linienfarbe: LineColor.

// +++ Program start +++
//+------------------------------------------------------------------+
//|                                                         APFD.mq5 |
//|                                            Aktiniy ICQ:695710750 |
//|                                                    ICQ:695710750 |
//+------------------------------------------------------------------+
#property copyright "Aktiniy ICQ:695710750"
#property link      "ICQ:695710750"
#property version   "1.00"
//--- Indicator plotting in a separate window
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1
//--- plot Label1
#property indicator_label1  "APFD"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_style1  STYLE_SOLID
#property indicator_color1  clrRed,clrGold
#property indicator_width1  1
//--- Set the input parameters
input int   History=10000;
input int   Cell=5;
input int   CellForChange=3;
input color ColorUp=clrRed;
input color ColorDown=clrGold;
input color LineColor=clrAqua;
//--- Declare indicator buffers
double CandlesBufferOpen[];
double CandlesBufferHigh[];
double CandlesBufferLow[];
double CandlesBufferClose[];
double CandlesBufferColor[];
//--- Array for copying calculation data from the minute bars
double OpenPrice[];
// Variables for calculations
double PriceNow=0;
double PriceBefore=0;
//--- Introduce auxiliary variables
char   Trend=0;      // Direction of the price trend
double BeginPrice=0; // Starting price for the calculation
char   FirstTrend=0; // Direction of the initial market trend
int    Columns=0;    // Variable for the calculation of columns
double InterimOpenPrice=0;
double InterimClosePrice=0;
double NumberCell=0; // Variable for the calculation of cells
double Tick=0;       // Tick size
double OldPrice=0;   // Value of the last calculation price
//--- Create arrays to temporary store data on column opening and closing prices
double InterimOpen[];
double InterimClose[];
// +++ Program start +++

Wenden wir uns nun der Funktion OnInit() zu. Sie soll Indikatorpuffer mit eindimensionalen Arrarys verknüpfen. Wir werden außerdem den Wert des Indikators festsetzen, ohne dass wir eine genauere Anzeige einstellen,

und wir werden den Wert mithilfe der Hilfsvariable Tick (Größe eines Ticks) kalkulieren. Zusätzlich legen wir noch Farbschema und Indexing Order in den Indikatorpuffern als Zeitserie fest. Dies macht es leichter, den Wert des Indikators zu berechnen.

// +++ The OnInit function +++
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,CandlesBufferOpen,INDICATOR_DATA);
   SetIndexBuffer(1,CandlesBufferHigh,INDICATOR_DATA);
   SetIndexBuffer(2,CandlesBufferLow,INDICATOR_DATA);
   SetIndexBuffer(3,CandlesBufferClose,INDICATOR_DATA);
   SetIndexBuffer(4,CandlesBufferColor,INDICATOR_COLOR_INDEX);
//--- Set the value of the indicator without rendering
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
//--- Calculate the size of one tick
   Tick=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE);
//--- Set the color scheme
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,ColorUp);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,1,ColorDown);
//--- Set the indexing order in arrays as time series
   ArraySetAsSeries(CandlesBufferClose,true);
   ArraySetAsSeries(CandlesBufferColor,true);
   ArraySetAsSeries(CandlesBufferHigh,true);
   ArraySetAsSeries(CandlesBufferLow,true);
   ArraySetAsSeries(CandlesBufferOpen,true);
//--- Check the input parameter for correctness
   if(CellForChange<2)
      Alert("The CellForChange parameter must be more than 1 due to plotting peculiarities");
//---
   return(0);
  }
// +++ The OnInit function +++

Wir haben nun das „Herz“ des Indikators - die Funktion OnCalculate() - erreicht. Dies ist der Ort, an dem alle Kalkulationen stattfinden. Kalkulationen von Indikatorwerten werden in 6 verschiedene Funktionen unterteilt, die mittels OnCalculate() aufgerufen werden. Sehen wir Sie uns ein wenig genauer an:

1.  Funktion zum Kopieren von Daten

Diese Funktion kopiert Daten zwecks Kalkulationen von einem Minutenbalken in Richtung eines Arrays. Zuerst ändern wir die Größe des Arrays und kopieren dann den Eröffnungskurs hinein, indem wir die Funktion CopyOpen() verwenden.

//+------------------------------------------------------------------+
//| Function for copying data for the calculation                    |
//+------------------------------------------------------------------+
int FuncCopy(int HistoryInt)
  {
//--- Resize the array for copying calculation data
   ArrayResize(OpenPrice,(HistoryInt));
//--- Copy data from the minute bars to the array
   int Open=CopyOpen(Symbol(),PERIOD_M1,0,(HistoryInt),OpenPrice);
//---
   return(Open);
  }

2.  Funktion zur Berechnung der Anzahl an Säulen

Diese Funktion generiert die Säulenanzahl eines Point-&-Figure-Charts.

Kalkulationen erfolgen hierbei mithilfe einer Schleife, die über die Anzahl der Minutenbalken iteriert, die in die obere Funktion kopiert wurden. Die Schleife selbst besteht aus drei Hauptblöcken (unterschiedliche Trendtypen):

  •  0 - unbestimmter Trend.
  •  1 - Aufwärtstrend.
  • -1 - Abwärtstrend.

Der unbestimmte Trend wird nur dafür benutzt, um die anfängliche Kursbewegung zu bestimmen. Die Richtung der Kursbewegung wird bestimmt, wenn der absolute Wert der Differenz des aktuellen Marktes und des Initialkurses den Wert des Produkts aus Boxgröße und Auflösungsbetrag übersteigt.

Falls es einen nach unten gerichteten Ausbruch geben sollte, wird der anfängliche Trend als ein Abwärtstrend identifiziert. Im Anschluss daran wird ein entsprechender Eintrag in der Trend-Variable vermerkt. Ein Aufwärtstrend wird auf die entgegengesetzte Weise identifiziert. Außerdem erhöht sich ColumnsInt, der Wert der Variable für die Anzahl an Säulen.

Sobald der aktuelle Trend identifiziert ist, setzen wir zwei Bedingungen für jede Richtung. Wenn der Kurs sich weiterhin in die Richtung des aktuellen Trends (Boxgröße) bewegen sollte, bleibt der Wert der Variable ColumnsInt unverändert. Sollte der Kurs um das Produkts aus Boxgröße und Auflösungsbetrag zurückgehen, so wird eine neue Säule erscheinen und der Wert der Variable ColumnsInt wird sich um eins erhöhen.

Und immer so weiter bis alle Säulen identifiziert worden sind. 

Um die Summe der Zellen in der Schleife zu runden, werden wir die Funktion MathRound() verwenden, die es uns erlaubt, den resultierenden Wert auf den nächsten Integer zu runden. Optional kann diese Funktion auch durch MathFloor() (rundet auf den nächsten Integer ab) oder MathCeil() (rundet auf den nächsten Integer auf) ersetzt werden - je nachdem welche grafische Darstellung gerade benötigt wird.

//+------------------------------------------------------------------+
//| Function for calculating the number of columns                   |
//+------------------------------------------------------------------+
int FuncCalculate(int HistoryInt)
  {
   int ColumnsInt=0;

//--- Zero out auxiliary variables
   Trend=0;                 // Direction of the price trend
   BeginPrice=OpenPrice[0]; // Starting price for the calculation
   FirstTrend=0;            // Direction of the initial market trend
   Columns=0;               // Variable for the calculation of columns
   InterimOpenPrice=0;
   InterimClosePrice=0;
   NumberCell=0;            // Variable for the calculation of cells
//--- Loop for the calculation of the number of main buffers (column opening and closing prices)
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         //--- Downtrend
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice;
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
           }
         //--- Uptrend
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice;
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
           }
         BeginPrice=InterimClosePrice;
         ColumnsInt++;
         FirstTrend=Trend;
        }
      //--- Determine further actions in case of the downtrend
      if(Trend==-1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
            BeginPrice=InterimClosePrice;
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            ColumnsInt++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice+(Cell*Tick);
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
            BeginPrice=InterimClosePrice;
           }
        }
      //--- Determine further actions in case of the uptrend
      if(Trend==1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            ColumnsInt++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice-(Cell*Tick);
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
            BeginPrice=InterimClosePrice;
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
            BeginPrice=InterimClosePrice;
           }
        }
     }
//---
   return(ColumnsInt);
  }

3.  Funktion zum Färben von Spalten

Diese Funktion dient dazu, Säulen entsprechend ihrer Notwendigkeit zu färben, indem ein vorgegebenes Farbschema verwendet wird. Zu diesem Zwecke werden wir eine Schleife schreiben, die über eine Anzahl von Säulen iteriert, und wir werden die erforderlichen Farben für gerade und ungerade Säulen einstellen, indem wir den Anfangstrendwert (Anfangssäule) berücksichtigen werden.

//+------------------------------------------------------------------+
//| Function for coloring columns                                    |
//+------------------------------------------------------------------+
int FuncColor(int ColumnsInt)
  {
   int x;
//--- Fill the buffer of colors for drawing
   for(x=0; x<ColumnsInt; x++)
     {
      if(FirstTrend==-1)
        {
         if(x%2==0) CandlesBufferColor[x]=1; // All even buffers of color 1
         if(x%2>0) CandlesBufferColor[x]=0;  // All odd buffers of color 0
        }
      if(FirstTrend==1)
        {
         if(x%2==0) CandlesBufferColor[x]=0; // All odd buffers of color 0
         if(x%2>0) CandlesBufferColor[x]=1;  // All even buffers of color 1
        }
     }
//---
   return(x);
  }

Funktion zum Bestimmen der Spaltengröße

Sobald wir die Anzahl der zu verwendenden Säulen und der notwendigen Farben bestimmt haben, müssen wir noch die Höhe der Säulen definieren. Hierfür werde wir temporäre Arrays kreieren, InterimOpen[] und InterimClose[], in denen wir die Eröffnungs- und Endkurse einer jeden Säule speichern werden. Die Größe dieser Arrays entspricht genau der Nummer der Säulen.

Unsere Schleife wird dann beinahe identisch mit der Schleife der Funktion FuncCalculate() sein. Ihr Unterschied im Vergleich zu allen obigen besteht darin, dass sie außerdem die Eröffnungs- und Endkurse einer jeden Säule speichert. Diese Trennung wird aus dem Grund durchgeführt, damit man die Zahl der Chartsäulen im Vorfeld kennt. Wir könnten theoretisch bewusst eine größere Anzahl an Säulen für die Speicherallokation des Arrays einstellen und nur eine Schleife verwenden. Allerdings würden wir dann auch mehr Ressourcen benötigen.

Lassen Sie uns einen Blick darauf werfen, wie wir die Höhe der Boxen bestimmen. Nachdem der Kurs eine Distanz zurückgelegt hat, die der Anzahl benötigter Boxgrößen entspricht, kalkulieren wir die Nummer und runden sie auf ihren nächsten Integer. Dann addieren wir die Gesamtanzahl der Boxgrößen der aktuellen Säule zur Säule des Eröffnungskurses hinzu. Hierdurch erhalten wir die Schlusskurssäule, die somit zum letzten benutzten Kurs wird. Sie wird uns als Ausgangspunkt für viele weitere Aktionen dienen.

//+------------------------------------------------------------------+
//| Function for determining the column size                         |
//+------------------------------------------------------------------+
int FuncDraw(int HistoryInt)
  {
//--- Determine the sizes of temporary arrays
   ArrayResize(InterimOpen,Columns);
   ArrayResize(InterimClose,Columns);
//--- Zero out auxiliary variables
   Trend=0;                 // Direction of the price trend
   BeginPrice=OpenPrice[0]; // Starting price for the calculation
   NumberCell=0;            // Variable for the calculation of cells
   int z=0;                 // Variable for indices of temporary arrays
//--- Loop for filling the main buffers (column opening and closing prices)
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         //--- Downtrend
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice;
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
           }
         //--- Uptrend
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice;
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); // Normalize the number of decimal places
            Trend=1;
           }
         BeginPrice=InterimClose[z];
        }
      //--- Determine further actions in case of the downtrend
      if(Trend==-1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
            BeginPrice=InterimClose[z];
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            z++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice+(Cell*Tick);
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=1;
            BeginPrice=InterimClose[z];
           }
        }
      //--- Determine further actions in case of the uptrend
      if(Trend==1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            z++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice-(Cell*Tick);
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
            BeginPrice=InterimClose[z];
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=1;
            BeginPrice=InterimClose[z];
           }
        }
     }
//---
   return(z);
  }

5.  Funktion zur Umkehrung eines Arrays

Diese Funktion kehrt die erhaltenen Säulen-Array-Daten um, um den Chart programmatisch von rechts nach links anzuzeigen. Die Array-Umkehrung erfolgt dabei in Form einer Schleife, mit Hoch- und Tiefwerten, die den Kerzencharts zugeordnet werden. Dies geschieht, da der Indikator nur für die Kerzencharts angezeigt wird, für die der Indikatorspeicher Werte aufweist, die ungleich 0 sind.

//+------------------------------------------------------------------+
//| Function for array reversal                                      |
//+------------------------------------------------------------------+
int FuncTurnArray(int ColumnsInt)
  {
//--- Variable for array reversal
   int d=ColumnsInt;
   for(int x=0; x<ColumnsInt; x++)
     {
      d--;
      CandlesBufferOpen[x]=InterimOpen[d];
      CandlesBufferClose[x]=InterimClose[d];
      if(CandlesBufferClose[x]>CandlesBufferOpen[x])
        {
         CandlesBufferHigh[x]=CandlesBufferClose[x];
         CandlesBufferLow[x]=CandlesBufferOpen[x];
        }
      if(CandlesBufferOpen[x]>CandlesBufferClose[x])
        {
         CandlesBufferHigh[x]=CandlesBufferOpen[x];
         CandlesBufferLow[x]=CandlesBufferClose[x];
        }
     }
//---
   return(d);
  }

Funktion zum Zeichnen horizontaler Linien

Diese Funktion erschafft ein Gitter von „Boxen“, indem horizontale Linien (Objekte) verwendet werden. Am Anfang der Funktion bestimmen wir den minimalen wie auch den maximalen Preiswert der kalkulierten Daten des Arrays. Diese Werte werden später dazu dienen, um Linien ober- und unterhalb des Startpunktes zu zeichnen.

//+------------------------------------------------------------------+
//| Function for drawing horizontal lines                            |
//+------------------------------------------------------------------+
int FuncDrawHorizontal(bool Draw)
  {
   int Horizontal=0;
   if(Draw==true)
     {
      //--- Create horizontal lines (lines for separation of columns)
      ObjectsDeleteAll(0,ChartWindowFind(),OBJ_HLINE); // Delete all old horizontal lines
      int MaxPriceElement=ArrayMaximum(OpenPrice);     // Determine the maximum price level
      int MinPriceElement=ArrayMinimum(OpenPrice);     // Determine the minimum price level
      for(double x=OpenPrice[0]; x<=OpenPrice[MaxPriceElement]+(Cell*Tick); x=x+(Cell*Tick))
        {
         ObjectCreate(0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),0,NormalizeDouble(x,Digits()));
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_WIDTH,1);
         Horizontal++;
        }
      for(double x=OpenPrice[0]-(Cell*Tick); x>=OpenPrice[MinPriceElement]; x=x-(Cell*Tick))
        {
         ObjectCreate(0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),0,NormalizeDouble(x,Digits()));
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_WIDTH,1);
         Horizontal++;
        }
      ChartRedraw();
     }
//---
   return(Horizontal);
  }

Nachdem wir nun alle Basisfunktionen beschrieben haben, sollten wir uns die Reihenfolge ihrer Aufrufe in OnCalculate() ansehen:

  • Startet die Funktion, um Daten für die Berechnungen zu kopieren (vorausgesetzt, dass es noch keine Kalkulationsbalken gibt).
  • Ruft die Funktion auf, um die Anzahl der Spalten zu berechnen.
  • Bestimmt die Farbe der Spalten.
  • Bestimmt die Größe der Spalten.
  • Ruft die Funktion für eine Datenumkehr in den Arrays auf.
  • Ruft die Funktion auf, um eine horizontale Linien zu zeichnen, die die Spalten in „Boxen“ umwandeln werden.
// +++ Main calculations and plotting +++
//+------------------------------------------------------------------+
//| 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[])
  {
//--- Reverse the array to conveniently get the last price value
   ArraySetAsSeries(close,true);
//---
   if(prev_calculated==0)
     {
      //--- Start the function for copying data for the calculation
      int ErrorCopy=FuncCopy(History);
      //--- In case of error, print the message
      if(ErrorCopy==-1)
        {
         Alert("Failed to copy. Data is still loading.");
         return(0);
        }
      //--- Call the function for calculating the number of columns
      Columns=FuncCalculate(History);
      //--- Call the function for coloring columns
      int ColorCalculate=FuncColor(Columns);
      //--- Call the function for determining column sizes
      int z=FuncDraw(History);
      //--- Start the function for array reversal
      int Turn=FuncTurnArray(Columns);
      //--- Start the function for drawing horizontal lines
      int Horizontal=FuncDrawHorizontal(true);
      //--- Store the value of the last closing price in the variable
      OldPrice=close[0];
     }
//--- If the price is one box size different from the previous one, 
//--- the indicator is recalculated
   if(fabs((OldPrice-close[0])/Tick)>Cell)
      return(0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
// +++ Main calculations and plotting +++

Dies ist das Ende der Kerncodes des Indikators. Da der Indikator allerdings seine Nachteile hat - sprich: komplexe Arrays - muss er manchmal neu geladen werden.

Um dies durchzuführen, werden wir auf die Funktion OnChartEvent() zurückgreifen, die alle Ereignisse des Drückens des „С"-(löschen) sowie des „R“-Buttons (neu entwerfen) verarbeitet. Zum Löschen wird einem Indikatorpuffer einfach eine 0 zugewiesen. Die Funktion zum Neuentwerfen eines Charts stellt im Prinzip eine Wiederholung der vorherigen Kalkulationen des Wertes und dessen Zuweisung zum Indikatorpuffer dar.

// +++ Secondary actions for the "С" key - clear and the "R" key - redraw +++
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- 67 - The "C" key code clears the indicator buffer
      if(lparam==67)
        {
         for(int x=0; x<Bars(Symbol(),PERIOD_CURRENT); x++)
            CandlesBufferOpen[x]=0;
         ChartRedraw();
        }
      // 82 - The "R" key code redraws the indicator
      if(lparam==82)
        {
         //--- Start the copying function
         int ErrorCopy=FuncCopy(History);
         //--- In case of error, print the message
         if(ErrorCopy==-1)
            Alert("Failed to copy data.");
         //--- Call the function for calculating the number of columns
         Columns=FuncCalculate(History);
         //--- Call the function for coloring columns
         int ColorCalculate=FuncColor(Columns);
         //--- Call the function for determining column sizes
         int z=FuncDraw(History);
         //--- Start the function for array reversal
         int Turn=FuncTurnArray(Columns);
         //--- Start the function for drawing horizontal lines
         int Horizontal=FuncDrawHorizontal(true);
        }
     }
  }
//+------------------------------------------------------------------+
// +++ Secondary actions for the "С" key - clear and the "R" key - redraw +++

Wir können nun erst einmal tief durchatmen, da wir den Algorithmus und den Indikatorcode erfolgreich hinter uns gebracht haben. Als Nächstes widmen wir uns den Point-&-Figure-Chartmustern, die Signale zur Durchführung von Trades generieren.

 

Standardsignale

Es gibt dabei zwei verschiedene Ansätze zum Analysieren von und Traden mit Point-&-Figure-Charts: die Verwendung von Mustern und die Verwendung von Unterstützungs- und Widerstandslinien. Der zweite ist relativ seltsam, da Unterstützungs- und Widerstandslinien in einem Winkel von 45 Grad gezeichnet werden (dies ist bei einem entworfenen Indikator nicht immer der Fall, da dieser mit einem Kerzenchart gezeichnet wird, dessen Größe von der Größe des Hauptcharts abhängt, was zu einem Winkelverzug führen kann).

Wenden wir uns nun den Mustern zu:

  1. „Double Top“ und „Double Bottom“-Muster.

    „Double Top“ tritt immer dann auf, wenn der Kurs zunächst steigt, dann fällt und eine Spalte von „O“s formt, dann wieder steigt und die ursprüngliche „X“-Spalte um eine Boxgröße übersteigt. Dieses Muster entspricht einem Kaufen-Signal.

    Ein „Double Bottom“- ist das genau Gegenteil eines „Double Top“-Musters. Es gibt einen Kursabfall (eine O-Säule), auf den eine X- und anschließend eine weitere O-Säule folgt, die eine Box unter die vorherige fällt, wodurch ein Verkaufen-Signal entsteht.

    Abb. 3 „Double Top“ und „Double Bottom“-Muster

    Abb. 3 „Double Top“ und „Double Bottom“-Muster

  2. Triple Top“ und „Triple Bottom“-Muster

    Diese Muster sind zwar weniger häufig, repräsentieren allerdings sehr starke Signale. Im Prinzip ähneln Sie den „Double Top“ und „Double Bottom“-Mustern, indem sie diese fortführen. Bevor sie ein Signal übertragen, wiederholen sie die Bewegungen der obigen Muster.

    Ein „Triple Top“ tritt immer dann ein, wenn der Kurs das gleiche Preislevel zweimal trifft und dann nach oben ausbricht, was ein Kaufen-Signal repräsentiert.

    Das „Triple Bottom“-Muster ist das Pendant zum „Triple Top“. Es tritt auf wenn der Kurs zweimal auf das gleiche Preislevel herabsinkt und dann nach unten ausbricht, was ein Verkaufen-Signal übermittelt.

    Abb. 4 „Triple Top“ und „Triple Bottom“-Muster.

    Abb. 4 „Triple Top“ und „Triple Bottom“-Muster.

  3. Symmetrical Triangle Breakout"-Muster: aufwärts und abwärts

    Wir erinnern uns an die technischen Analysemuster. Das „Symmetrical Triangle Breakout"-Muster ähnelt dem „Symmetrical Triangle“ in technischen Analysen. Ein Ausbruch nach oben (siehe untere, linke Abbildung) kommt einem Kaufen-Signal gleich. Umgekehrt bedeutet ein nach unten gerichteter Ausbruch ein Verkaufen-Signal (Abbildung rechts).

    Abb. 5 „Symmetrical Triangle Breakout“: aufwärts und abwärts.

    Abb. 5 „Symmetrical Triangle Breakout“: aufwärts und abwärts.

  4. Bullenhaftes-Katapult"- und „Bärenhaftes-Katapult"-Muster.

    „Katapulte“ ähneln in gewisser Weise dem „Aufsteigenden-Dreieck“- und „Absteigenden-Dreieck“-Muster einer technischen Analyse. Ihre Signale sind im Wesentlichen gleich: Sobald der Kurs über eine der parallelen Seiten des Dreiecks nach oben oder unten ausbricht, tritt ein Kaufen- bzw. Verkaufen-Signal auf. Im Falle eines „Bullenhaften Katapults“ bricht der Kurs nach oben aus - ein Kaufen-Signal (siehe linke Abb.). Im Falle eines „Bärenhaften Katapults“ liegt ein Ausbruch nach unten vor (siehe rechte Abb.), also ein Verkaufen-Signal.

    Abb. 6 „Bullenhaftes-Katapult“- und „Bärenhaftes-Katapult“-Muster

    Abb. 6 „Bullenhaftes-Katapult“- und „Bärenhaftes-Katapult“-Muster

  5. 45-Grad-Trendlinien"-Muster.

    Das „45-Grad-Trendlinien"-Muster kreiert eine Unterstützungs- oder Widerstandslinie. Falls es einen Breakout dieser Linie geben sollte, erhalten wir entweder ein Verkaufen- (wie in der Abbildung rechts zu erkennen) oder ein Kaufen-Signal (siehe linke Abbildung).

    Abb. 7 „45-Grad-Trendlinien"-Muster.

    Abb. 7 „45-Grad-Trendlinien"-Muster.

Wir haben uns die Muster und Signale eines üblichen Point-&-Figure-Charts zu Gemüte geführt. Begeben wir uns nun auf die Suche nach einigen von ihnen im Indikatorchart, der sich am Anfang des Artikels befindet.

Abb. 8 Muster und Signale in einem Point-&-Figure-Charts lokalisieren.

Abb. 8 Muster und Signale in einem Point-&-Figure-Charts lokalisieren.

 

Fazit

Wir befinden uns nun im finalen Abschnitt vorliegenden Artikels. Ich möchte an dieser Stelle sagen dass Point-&-Figure-Charts keine längst vergessenen Reliquien alter Zeiten darstellen, sondern, dass diese auch heute noch verwendet werden, was ihre Bedeutung unterstreicht.

Der entwickelte Indikator liefert relativ präzise Chart-Ergebnisse, wenngleich nicht vollkommen frei von Nachteilen wie beispielsweise dem Umstand, dass Blöcke anstelle von O/X gezeichnet werden, sowie dem Umstand der Unmöglichkeit, mittels Strategietester getestet zu werden (oder besser unkorrekte Operationen).

Ich möchte ferner anmerken, dass sich der leicht modifizierte Algorithmus theoretisch für Renko-Charts sowie für eine Kombination beider Charttypen (ein Code & spezifizierbare Menüoptionen) durchaus eignet. Ich schließe dabei die Möglichkeit nicht aus, den Chart direkt in das Hauptfenster zu zeichnen. Allerdings wird auch hierfür eine leicht modifizierte Version benötigt.

Grundsätzlich habe ich diesen Artikel mit der Idee im Hinterkopf verfasst, Ihnen meine ganz eigenen Ideen über die Indikatorentwicklung zu präsentieren. Da es sich hierbei um meinen ersten Artikel handelt, wäre ich Ihnen für entsprechendes Feedback außerordentlich verbunden. In jedem Fall danken ich Ihnen dafür, dass Sie sich die Zeit genommen haben, diesen Artikel zumindest zu überfliegen.

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

Beigefügte Dateien |
apfd.mq5 (18.85 KB)
MQL5 Cloud Network Kalkulieren Sie noch? MQL5 Cloud Network Kalkulieren Sie noch?

Die Veröffentlichung von MQL5 Cloud Network ist nun schon beinahe anderthalb Jahre her. Dieser Zeitpunkt läutete gewissermaßen den Beginn einer neuen Ära des algorithmischen Tradings ein - mit nur einigen wenigen Klicks stehen Tradern nun mehrere hundert bis tausend Computerkerne zur Verfügung, um Ihre Handelsstrategien zu optimieren.

MQL5-Kochbuch: Umgang mit typischen Chartereignissen MQL5-Kochbuch: Umgang mit typischen Chartereignissen

Dieser Artikel beschäftigt sich mit den üblichsten Chartereignissen und veranschaulicht deren Ablauf anhand von Beispielen. Wir werden uns auf Mausereignisse, Tastenanschläge, die Erstellung/Veränderung/Entfernung grafischer Objekte, Mausklicks auf einen Chart oder ein grafisches Objekt, das Verschieben eines solchen Objekts mit der Maus, das Beenden der Editierung eines Textes in einem Textfeld sowie die Modifikation von Chartereignissen fokussieren. Für jeden Ereignistyp wird dabei ein Beispiel aus einer Reihe von MQL5-Programmen angeführt.

Zur Fehlerbehebung von MQL5-Programmen (Debugging) Zur Fehlerbehebung von MQL5-Programmen (Debugging)

Dieser Artikel richtet sich primär an Programmierer, die die Sprache zwar bereits gelernt haben, die allerdings noch keine Meister ihres Fachs sind. Er wird auf verschiedene Debugging-Techniken eingehen, die der gebündelten Erfahrung des Autors sowie vieler anderer Programmierer entspringen.

Der ZigZag-Indikator: Frischer Ansatz und Neue Lösungen Der ZigZag-Indikator: Frischer Ansatz und Neue Lösungen

Dieser Beitrag beschäftigt sich mit der Möglichkeit, einen fortgeschrittenen ZigZag-Indikator zu erzeugen. Das Konzept der Identifikation von Knoten beruht auf der Verwendung des Envelopes-Indikators. Wir gehen davon aus, dass wir eine bestimmte Kombination von Eingabe-Parametern für eine Reihe von Envelopes finden können, bei denen alle ZigZag-Knoten innerhalb der Grenzen der Envelopes-Bänder liegen. Als Konsequenz können wir daher versuchen, die Koordinaten des neuen Knoten vorherzusagen.