Die Momentum-Pinball Handelsstrategie

Alexander Puzanov | 17 Januar, 2018


Einführung

In diesem Artikel setzen wir die Programmierung der Handelsstrategien fort, die im Buche "Street Smarts: High Probability Short-Term Trading Strategies" von L. Raschke und L. Connors beschrieben ist. Sie sind den Tests der Grenzen von Preisspannen gewidmet. Die letzte vollwertige Handelsstrategie (HS), Momentum-Pinball, arbeitet mit einem Muster aus zwei Tagesbalken. Durch den ersten wird die Handelsrichtung des zweiten Tages definiert, und die Preisbewegung am Anfang der zweiten Balken legt bestimmte Handelsniveaus für Ein- und Ausstiege aus dem Markt fest.

Der Zweck dieses Artikels ist es, den Programmierern, die MQL5 bereits beherrschen, eine Variante der Realisierung von Momentum-Pinball zu demonstrieren, in der vereinfachte Methoden der objektorientierten Programmierung angewendet werden. Von einer vollwertigen OOP unterscheidet sich der Code durch das Fehlen von Klassen - sie werden durch Strukturen ersetzt. Im Gegensatz zu Klassen unterscheiden sich Code und Programm nur geringfügig von der Art der Programmierung, die den meisten beginnenden Programmierer vertraut ist. Andererseits reichen die Eigenschaften, die die Strukturen bereitstellen, mehr als aus, um diese Aufgaben zu lösen.

Wie im vorherigen Artikel, erstellen wir zunächst den Signalteil, dann einen Indikator für den manuellen Handel und, um die relevanten Balken im Chart zu kennzeichnen. Das dritte Programm wird der Expert Advisor für den automatisierten Handel sein; es wird auch den Signalteil verwenden. Abschließend werden wir den Expert Advisor mit neuen Kursen testen, da die Autoren des Buches mit 20-Jahre alten Kursen gearbeitet haben.


Die Regeln der HS Momentum-Pinball

L. Raschke und L. Connors sahen sich mit Unsicherheit konfrontiert, weswegen sie die von George Taylor beschriebenen Handelstechniken einsetzten, die sich durch eine Reihe von Regeln dieser Handelsstrategie beschreiben lässt. Taylors Strategie legt vor dem Handelstag die Richtung fest - seien es lauter Käufe oder lauter Verkäufe. Allerdings verstößt der tatsächliche Handel des Autors oft gegen diese Regelung, die nach Ansicht der Buchautoren die Handelsregeln durcheinander bringen würde.

Um die Handelsrichtung des nächsten Tages genauer bestimmen zu können, haben die Autoren den Indikator ROC (Rate Of Change) verwendet. Der Oszillator RSI (Relative Strength Index) wurde auf seine Werte angewendet, welches die und die Zyklizität der ROC-Werte ist gut sichtbar macht. Schließlich fügten die Autoren der HS Signalniveaus hinzu - Grenzen von überkauften und überverkauften Bereichen auf dem RSI-Chart. Das Vorhandensein der Linie eines solchen Indikators LBR/RSI (benannt nach Linda Bradford Raschke LBR/RSI) in einem entsprechenden Bereich ist dazu bestimmt, die wahrscheinlichsten Verkaufs- und Kauftage zu erkennen. LBR/RSI wird im Folgenden näher erläutert.

Die vollständigen Regeln des Momentum-Pinball TS für Buy-Einträge sind wie folgt formuliert.

  1. Auf D1 sollte der Wert von LBR/RSI des letzten geschlossenen Tages innerhalb des überkauften Bereichs liegen - unter 30.
  2. Nachdem der erste Einstunden-Balken des Tages sich geschlossen hat, platzieren Sie ein Pending-Buy (schwebender Kaufauftrag) über dem Hoch dieses Balkens.
  3. Nach dem Auslösen der Pending-OrderOrder platzieren Sie Stop-Loss auf den Tief des ersten Einstunden-Balkens.
  4. Wenn eine Position mit Verlust geschlossen wurde, platzieren Sie den Pending-Sell (schwebenden Verkaufsauftrag) erneut auf dem gleichen Niveau.
  5. Wenn die Position bis zum Ende des Tages profitabel bleibt, halten Sie sie für den nächsten Tag. Am zweiten Handelstag muss die Position geschlossen werden.

Die Visualisierung der Regeln für die Positionseröffnung mit Hilfe der beiden unten beschriebenen Indikatoren sieht folgendermaßen aus:

— LBR/RSI auf Tages-Zeitrahmen ist im überverkauften Bereich (siehe 30. Oktober 2017)


— Der Indikator TS_Momentum_Pinball eines beliebigen Zeitrahmens (von M1 bis D1) zeigt die Handelsniveaus und Preisspanne der ersten Stunde des Tages, anhand derer die Niveaus berechnet werden:


Die Regeln für den Ausstieg aus dem Markt sind im Buch nicht klar definiert: Die Autoren sagen etwas über die Verwendung von Trailing-Stopps, über das Schließen am nächsten Morgen und über den Ausstieg über dem Hoch des ersten Handelstages.

Die Regeln für Verkaufe sind ähnlich - LBR/RSI sollte innerhalb des überkauften Bereichs liegen (über 70), eine Pending-Order sollte auf dem Tief des ersten Stundenbalkens platziert werden.



LBR/RSI Indikator

Natürlich können alle Berechnungen, die für ein Signal notwendig sind, im Signalteil selbst durchgeführt, aber neben dem automatisierten Handel soll der Artikel auch den manuellen Handel ermöglichen. Ein eigener Indikator LBR/RSI mit Hervorhebung der überkauften/überkauften Bereiche ist nützlich, um das Erkennen der Muster erleichtern. Und um unsere Bemühungen zu optimieren, werden wir nicht zwei verschiedene Versionen des LBR/RSI programmieren (mit Puffer für den Indikator und ohne für den Roboter). Über die Standardfunktion iCustom greifen wir auf den externen Indikator zu. Dieser Indikator führt keine aufwendige Bewertungen durch und muss nicht bei jedem Tick geprüft werden - die HS wird der Wert des Indikators des geschlossenen Tagesbalken verwendet. Einen sich ständig ändernden Zeitwert können wir ignorieren. Daher gibt es keine wesentlichen Hindernisse für eine solche Lösung.

Hier vereinigen sich ROC und RSI, die den resultierenden Oszillator zeichnen. Um die benötigten Werte leicht zu erkennen, werden die überkauften und überverkauften Flächen in verschiedenen Farben gezeichnet. Für die Anzeige benötigen wir fünf Puffer und vier weitere - für Hilfsberechnungen.

Zu den Standardeinstellungen (Periodenlänge des RSI und die Werte der Grenzen der beiden Bereiche) fügen wir eine weitere hinzu, die nicht Teil der Regeln des Handelssystems sind. Es wird möglich sein, nicht nur den Schlusskurs des täglichen Balkens für Berechnungen zu verwenden, sondern auch den aussagekräftigeren Median, den typischen oder gewichteten Durchschnittspreis. Tatsächlich kann der Benutzer für seine Tests eine der sieben Optionen auswählen, die von der Enumeration ENUM_APPLIED_PRICE bereitgestellt werden.

Die Deklaration von Puffern, nutzerspezifischen Textfeldern und dem Initialisierungsblock sieht wie folgt aus:

#property indicator_separate_window
#property indicator_buffers  9
#property indicator_plots    3
#property indicator_label1  “Overbought area"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C'255,208,234'
#property indicator_width1  1
#property indicator_label2  “Oversold area"
#property indicator_type2   DRAW_FILLING
#property indicator_color2  C'179,217,255'
#property indicator_width2  1
#property indicator_label3  "RSI от ROC"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_SOLID
#property indicator_color3  clrTeal
#property indicator_width3  2
#property indicator_minimum 0
#property indicator_maximum 100
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;  // Preis zur Berechnung des ROC
input uint    TS_MomPin_RSI_Period = 3;                           // RSI Periodenlänge
input double  TS_MomPin_RSI_Overbought = 70;                      // RSI Überverkauftniveau
input double  TS_MomPin_RSI_Oversold = 30;                        // RSI Überkauftniveau
double
  buff_Overbought_High[], buff_Overbought_Low[],                  // überkaufter Bereich, Hintergrund
  buff_Oversold_High[], buff_Oversold_Low[],                      // überverkaufter Bereich, Hintergrund
  buff_Price[],                                                   // Array der Berechnungspreise
  buff_ROC[],                                                     // ROC-Array
  buff_RSI[],                                                     // RSI des ROC
  buff_Positive[], buff_Negative[]                                // Hilfsarray zur Berechnung des RSI
;
int OnInit() {
  // Zweck des Puffers:
  
  // Überkaufter Bereich
  SetIndexBuffer(0, buff_Overbought_High, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(0, PLOT_SHOW_DATA, false);
  SetIndexBuffer(1, buff_Overbought_Low, INDICATOR_DATA);
  
  // Überverkaufter Bereich
  SetIndexBuffer(2, buff_Oversold_High, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
  SetIndexBuffer(3, buff_Oversold_Low, INDICATOR_DATA);
  
  // RSI
  SetIndexBuffer(4, buff_RSI, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
  
  // Hilfspuffer für den RSI
  SetIndexBuffer(5, buff_Price, INDICATOR_CALCULATIONS);
  SetIndexBuffer(6, buff_ROC, INDICATOR_CALCULATIONS);
  SetIndexBuffer(7, buff_Negative, INDICATOR_CALCULATIONS);
  SetIndexBuffer(8, buff_Positive, INDICATOR_CALCULATIONS);
  
  IndicatorSetInteger(INDICATOR_DIGITS, 2);
  IndicatorSetString(INDICATOR_SHORTNAME, "LBR/RSI");
  
  return(INIT_SUCCEEDED);
  }

In der Funktion OnCalculate, gibt es zwei Schleifen: die erste bereitet die Werte des Arrays des ROC vor, die zweite berechnet damit die Werte des Oszillators.

In der Version des Rates Of Change von Linda & Raschke, sollten wir den Wert nicht mit dem der direkt benachbarten Balken vergleichen, sondern zumindest mit dem übernächsten. Das heißt, die HS verwendet Preisänderungen von Tagen, die getrennt durch ein und drei Handelstage. Es ist nicht schwer, dies zu tun, aber gleichzeitig zeichnen wir den Hintergrund der überkauften und überverkauften Bereiche. Und vergessen Sie nicht, die Art des Preise zu wählen:

int
  i_RSI_Period = int(TS_MomPin_RSI_Period),         // sichern der Periodenlänge des RSI
  i_Bar, i_Period_Bar                               // zwei Indices zur gleichzeitigen Berechnung
;
double
  d_Sum_Negative, d_Sum_Positive,                   // Hilfsvariablen zur Berechnung des RSI
  d_Change                                          // Hilfsvariablen zur Berechnung des ROC
;
// Ausfüllen des ROC-Puffers und der Bereiche:
i_Period_Bar = 1;
while(++i_Period_Bar < rates_total && !IsStopped()) {
// Preis des Balken:
  switch(TS_MomPin_Applied_Price) {
    case PRICE_CLOSE:     buff_Price[i_Period_Bar] = Close[i_Period_Bar]; break;
    case PRICE_OPEN:      buff_Price[i_Period_Bar] = Open[i_Period_Bar]; break;
    case PRICE_HIGH:      buff_Price[i_Period_Bar] = High[i_Period_Bar]; break;
    case PRICE_LOW:       buff_Price[i_Period_Bar] = Low[i_Period_Bar]; break;
    case PRICE_MEDIAN:    buff_Price[i_Period_Bar] = 0.50000 * (High[i_Period_Bar] + Low[i_Period_Bar]); break;
    case PRICE_TYPICAL:   buff_Price[i_Period_Bar] = 0.33333 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar]); break;
    case PRICE_WEIGHTED:  buff_Price[i_Period_Bar] = 0.25000 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar] + Open[i_Period_Bar]); break;
  }
  // ROC:
  if(i_Period_Bar > 1) buff_ROC[i_Period_Bar] = buff_Price[i_Period_Bar] - buff_Price[i_Period_Bar - 2];
  
  // Hintergrund:
  buff_Overbought_High[i_Period_Bar] = 100;
  buff_Overbought_Low[i_Period_Bar] = TS_MomPin_RSI_Overbought;
  buff_Oversold_High[i_Period_Bar] = TS_MomPin_RSI_Oversold;
  buff_Oversold_Low[i_Period_Bar] = 0;
    }

Die zweite Schleife (RSI-Berechnung) hat keine Besonderheiten, sie folgt praktisch dem Algorithmus eines Standardoszillators dieses Typs:

i_Period_Bar = prev_calculated - 1;
if(i_Period_Bar <= i_RSI_Period) {
  buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = d_Sum_Positive = d_Sum_Negative = 0;
  i_Bar = 0;
  while(i_Bar++ < i_RSI_Period) {
    buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = 0;
    d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
    d_Sum_Positive += (d_Change > 0 ? d_Change : 0);
    d_Sum_Negative += (d_Change < 0 ? -d_Change : 0);
  }
  buff_Positive[i_RSI_Period] = d_Sum_Positive / i_RSI_Period;
  buff_Negative[i_RSI_Period] = d_Sum_Negative / i_RSI_Period;
  
  if(buff_Negative[i_RSI_Period] != 0)
    buff_RSI[i_RSI_Period] = 100 - (100 / (1. + buff_Positive[i_RSI_Period] / buff_Negative[i_RSI_Period]));
  else
    buff_RSI[i_RSI_Period] = buff_Positive[i_RSI_Period] != 0 ? 100 : 50;
  
  i_Period_Bar = i_RSI_Period + 1;
}

i_Bar = i_Period_Bar - 1;
while(++i_Bar < rates_total && !IsStopped()) {
  d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
  
  buff_Positive[i_Bar] = (buff_Positive[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change> 0 ? d_Change : 0)) / i_RSI_Period;
  buff_Negative[i_Bar] = (buff_Negative[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change <0 ? -d_Change : 0)) / i_RSI_Period;
  
  if(buff_Negative[i_Bar] != 0)
    buff_RSI[i_Bar] = 100 - 100. / (1. + buff_Positive[i_Bar] / buff_Negative[i_Bar]);
  else
    buff_RSI[i_Bar] = buff_Positive[i_Bar] != 0 ? 100 : 50;
}

Nennen wir den Indikator LBR_RSI.mq5 und sichern wir ihn im Standardverzeichnis der Indikatoren des Terminals. Unter diesem Namen wird er durch die Funktion iCustom im Signalteil aufgerufen, sie sollten ihn daher nicht ändern.

Signalteil

Im Signalteil, wo sich der Expert Advisor mit dem Indikator verbindet, legen Sie die Benutzereinstellungen der Momentum-Pinball Handelsstrategie fest. Die Autoren liefern unveränderliche Werte für die Berechnung des LBR/RSI-Indikators (Periode RSI = 3, überkaufte Stufe = 30, überverkaufte Stufe = 70). Aber wir werden sie für die Tests veränderlich machen, genau wie die Methoden, Positionen zu schließen - das Buch erwähnt drei Varianten. Wir werden sie alle programmieren und der Benutzer kann die gewünschte Option auswählen:

  • Schließen der Position durch Stop-Loss bzw. einen Trailing-Stop
  • Schließen der Position am folgenden Tag
  • Warten auf ein Kreuzen des Hochs oder Tiefs des vorherigen Tages.

"Morgen" ist zu vage, um Regeln zu formalisieren, ist eine genauere Definition erforderlich. Raschke und Connors sagen dazu nichts, aber es ist vernünftig anzunehmen, dass die Bindung an den ersten Balken des neuen Tages (der in anderen Regeln der HS verwendet wird) auf den "Morgen" einer 24-Stunden-Zeitskala hinweist.

Vergessen wir nicht zwei weitere Einstellungen der HS - Abstand von den Extrema der ersten Stunde des Tages; die Abstände sollten zu den Preisen der Pending-Order führen und den Stop-Loss angeben:

enum ENUM_EXIT_MODE {     // Liste der Exit-Methoden
  CLOSE_ON_SL_TRAIL,      // Nur ein Trailing-Stop
  CLOSE_ON_NEW_1ST_CLOSE, // Schließen durch den ersten Balken des Folgetages
  CLOSE_ON_DAY_BREAK      // Schließen durch das Erreichen des Extremums des Eröffnungstages
};
// user settings
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;     // Momentum-Pinball: Preis zur Berechnung des ROC
input uint    TS_MomPin_RSI_Period = 3;                              // Momentum-Pinball: RSI Periodenlänge
input double  TS_MomPin_RSI_Overbought = 70;                         // Momentum-Pinball: RSI Überverkauftniveau
input double  TS_MomPin_RSI_Oversold = 30;                           // Momentum-Pinball: RSI Überkauftniveau
input uint    TS_MomPin_Entry_Offset = 10;                           // Momentum-Pinball: Abstand zu den Grenzen von H1 (in Points), Eröffnung
input uint    TS_MomPin_Exit_Offset = 10;                            // Momentum-Pinball: Abstand zu den Grenzen H1 (in Points) Position schließen
  input ENUM_EXIT_MODE  TS_MomPin_Exit_Mode = CLOSE_ON_SL_TRAIL;       // Momentum-Pinball: Methode Positionen im Gewinn zu schließen

Die Funktion fe_Get_Entry_Signal wird im Signalteil mit der vorherigen Handelsstrategie aus dem Buch Raschke und Connors, sowie mit weiteren ähnlichen Funktionen anderer HS vereint werden. Dies bedeutet, dass der Funktion eine Reihe von Parametern und Referenzen auf Variablen übergeben werden müssen, und der Rückgabewert vom gleichen Typ sein muss:

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(      // Zwei Balkenmuster  (D1 + H1)
  datetime  t_Time,                         // aktuelle Zeit
  double&    d_Entry_Level,                 // Eröffnungspreis (Referenz zur Variablen)
  double&    d_SL,                          // Stop-Loss (Referenz zur Variablen)
  double&    d_TP,                          // Take-Profit (Referenz zur Variablen)
  double&    d_Range_High,                  // Hoch des ersten Einstundenbalkens (Referenz zur Variablen)
  double&    d_Range_Low                    // Tief des ersten Einstundenbalkens (Referenz zur Variablen)
) {
  // function body
  }

Wie in der Vorgängerversion werden wir nicht alles neuberechnen, sobald ein Tick eintrifft. Stattdessen werden wir die berechneten Niveaus in statischen Variablen speichern. Allerdings wird die Arbeit mit dieser Funktion für den manuellen Handel Indikator sich stark unterscheiden; und das Nullsetzen der statischen Variablen beim Aufruf der Funktion vom Indikator sollte vorgesehen werden. Um zwischen den Aufruf durch einen Indikator von dem eines Roboter zu unterscheiden, verwenden Sie die Variable t_Time. Der Indikator invertiert ihn, d.h. er erstellt negative Werte:

static ENUM_ENTRY_SIGNAL se_Trade_Direction = ENTRY_UNKNOWN;   // Handelsrichtung heute
static double
  // Variablen, die die Niveaus speichern
  sd_Entry_Level = 0,
  sd_SL = 0, sd_TP = 0,
  sd_Range_High = 0, sd_Range_Low = 0
;
if(t_Time < 0) {                                               // nur bei Aufruf durch einen Indikator
  sd_Entry_Level = sd_SL = sd_TP = sd_Range_High = sd_Range_Low = 0;
  se_Trade_Direction = ENTRY_UNKNOWN;
}
// standardmäßig gelten früher gespeicherte Werte:
    d_Entry_Level = sd_Entry_Level; d_SL = sd_SL; d_TP = sd_TP; d_Range_High = sd_Range_High; d_Range_Low = sd_Range_Low;

Unten ist der Code für den ersten Aufruf des Indikators LBR/RSI:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // sichern des Handles des Indikators beim ersten Aufruf der Funktion:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) { // Handle des Indikator wurde nicht erhalten
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: error of receiving LBR_RSI indicator handle #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  }

Einmal in 24 Stunden sollte der Roboter den Indikatorwert des geschlossenen Tagesbalken vom Vortag analysieren und die für heute zulässige Handelsrichtung festlegen. Oder er sollte den Handel deaktivieren, wenn sich der LBR/RSI-Wert im neutralen Bereich befindet. Hier ist der Code für die Abfrage dieses Wertes aus dem Indikatorpuffer und seine Analyse, mit einer Protokollierungsfunktionen für möglicher Fehler und Besonderheiten des Aufrufs aus dem Indikator für den manuellen Handel:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // sichern des Handles des Indikators beim ersten Aufruf der Funktion:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) {       // Handle ist ungültig
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: error of indicator handle receipt LBR_RSI #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
}
// to find out the time of previous day daily bar:
datetime ta_Bar_Time[];
if(CopyTime(_Symbol, PERIOD_D1, fabs(t_Time), 2, ta_Bar_Time) < 2) {
  if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyTime: error #%u", __FUNCTION__, _LastError);
  return(ENTRY_INTERNAL_ERROR);
}
// previous day analysis, if this is the 1st call today:
static datetime st_Prev_Day = 0;
if(t_Time < 0) st_Prev_Day = 0;                     // nur für einen Aufruf von einem Indikator
if(st_Prev_Day < ta_Bar_Time[0]) {
  // Nullsetzen der Werte des Vortages:
  se_Trade_Direction = ENTRY_UNKNOWN;
  d_Entry_Level = sd_Entry_Level = d_SL = sd_SL = d_TP = sd_TP = d_Range_High = sd_Range_High = d_Range_Low = sd_Range_Low = 0;
  
  // retrieve value LBR/RSI of previous day:
  double da_Indicator_Value[];
  if(1 > CopyBuffer(si_Indicator_Handle, 4, ta_Bar_Time[0], 1, da_Indicator_Value)) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyBuffer: error #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // bei fehlerhaften Werten des LBR/RSI:
  if(da_Indicator_Value[0] > 100. || da_Indicator_Value[0] < 0.) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: Indicator buffer value error (%f)", __FUNCTION__, da_Indicator_Value[0]);
    return(ENTRY_UNKNOWN);
  }
  
  st_Prev_Day = ta_Bar_Time[0];                     // zählen der Versuche
  
  // sichern der Handelsrichung für heute:
  if(da_Indicator_Value[0] > TS_MomPin_RSI_Overbought) se_Trade_Direction = ENTRY_SELL;
  else se_Trade_Direction = da_Indicator_Value[0] > TS_MomPin_RSI_Oversold ? ENTRY_NONE : ENTRY_BUY;
  
  // ins Log:
  if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: Trading direction for %s: %s. LBR/RSI: (%.2f)",
    __FUNCTION__,
    TimeToString(ta_Bar_Time[1], TIME_DATE),
    StringSubstr(EnumToString(se_Trade_Direction), 6),
    da_Indicator_Value[0]
  );
  }

Wir kennen jetzt Handelsrichtung. Als Nächstes ermitteln wir die Preise zum Eröffnen und den Stop-Loss. Es reicht, das alle 24 Stunden einmal zu tun - direkt nach dem Ende des ersten Einstundenbalkens. Allerdings werden wir den Algorithmus wegen der Besonderheiten des manuellen Handels ein wenig komplizieren. Dies liegt daran, dass der Indikator Signale nicht nur in Echtzeit erkennt, sondern sie auch auf historischen Balken zeichnen soll:

// Keine Signalsuche heute
if(se_Trade_Direction == ENTRY_NONE) return(ENTRY_NONE);
// Analysieren des ersten H1-Balkens, falls nötig:
if(sd_Entry_Level == 0.) {
  // die letzten 24 H1-Balken abfragen:
  MqlRates oa_H1_Rates[];
  int i_Price_Bars = CopyRates(_Symbol, PERIOD_H1, fabs(t_Time), 24, oa_H1_Rates);
  if(i_Price_Bars == WRONG_VALUE) {                      // handling of CopyRates function error
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // unter 24 Balken finde den ersten von heute und sichere dessen Hoch und Tief:
  int i_Bar = i_Price_Bars;
  while(i_Bar-- > 0) {
    if(oa_H1_Rates[i_Bar].time < ta_Bar_Time[1]) break;      // last bar H1 of previous day
    
    // Extreme des H1-Balkens:
    sd_Range_High = d_Range_High = oa_H1_Rates[i_Bar].high;
    sd_Range_Low = d_Range_Low = oa_H1_Rates[i_Bar].low;
  }
  // falls der erster H1-Balken noch nicht beendet ist:
  if(i_Price_Bars - i_Bar < 3) return(ENTRY_UNKNOWN);
  
  // berechnen des Preise für den Handel:
  
  // Eröffnungspreis:  
  d_Entry_Level = _Point * TS_MomPin_Entry_Offset;           // Hilfsberechnung
  sd_Entry_Level = d_Entry_Level = se_Trade_Direction == ENTRY_SELL ? d_Range_Low - d_Entry_Level : d_Range_High + d_Entry_Level;
  // anfänglicher SL:  
  d_SL = _Point * TS_MomPin_Exit_Offset;                     // Hilfsberechnung
  sd_SL = d_SL = se_Trade_Direction == ENTRY_BUY ? d_Range_Low - d_SL : d_Range_High + d_SL;
  }

Danach können wir die Funktion mit der Rückgabe der Handelsrichtung beenden:

return(se_Trade_Direction);

Kommen wir nun zu den Bedingungen für das Schließen einer Position. Wir haben drei Varianten, von denen eine (Stop-Loss, Trailing-Stop) bereits im Code der Vorgängerversionen des Expert Advisors realisiert ist. Zwei weitere Varianten erfordern für die Berechnungen Eröffnungspreis, Eröffnungszeit und die Positionsrichtung. Wir übergeben alles mit der aktuellen Zeit und der gewählten Schließmethode der Funktion fe_Get_Exit_Signal:

ENUM_EXIT_SIGNAL fe_Get_Exit_Signal(    // Handelsrichtung
  double            d_Entry_Level,      // Eröffnungspreis
  datetime          t_Entry_Time,       // Eröffnungszeit
  ENUM_ENTRY_SIGNAL e_Trade_Direction,  // Handelsrichtung
  datetime          t_Current_Time,     // aktuelle Zeit
  ENUM_EXIT_MODE    e_Exit_Mode         // Methode Position zu schließen
) {
  static MqlRates soa_Prev_D1_Rate[];   // Daten des Vortages
  static int si_Price_Bars = 0;         // Hilfszähler
  if(t_Current_Time < 0) {              // Aufruf durch Indikator oder Expert Advisor
    t_Current_Time = -t_Current_Time;
    si_Price_Bars = 0;
  }
  double
    d_Curr_Entry_Level,
    d_SL, d_TP,
    d_Range_High,  d_Range_Low
  ;
  
  if(e_Trade_Direction < 1) {          // keine Position, alles auf Null setzen
    si_Price_Bars = 0;
  }
  
  switch(e_Exit_Mode) {
    case CLOSE_ON_SL_TRAIL:            // Schließen durch Trailing-Stop
            return(EXIT_NONE);
                      
    case CLOSE_ON_NEW_1ST_CLOSE:       // Schließen bei dem ersten Balken des nächsten Tages
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // Tag für die Positionseröffnung noch nicht definiert
            
            if(fe_Get_Entry_Signal(t_Current_Time, d_Curr_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low)
              < ENTRY_UNKNOWN
            ) {
              if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 1st bar of the following day is closed", __FUNCTION__);
              return(EXIT_ALL);
            }
            return(EXIT_NONE);         // nicht geschlossen
            
    case CLOSE_ON_DAY_BREAK:           // Bei Erreichen des Extremums des Eröffnungstages
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // noch kein Ende des Eröffnungstages
            
            if(t_Current_Time % 86400 > 36000) return(EXIT_ALL); // time out
            
            if(si_Price_Bars < 1) {
              si_Price_Bars = CopyRates(_Symbol, PERIOD_D1, t_Current_Time, 2, soa_Prev_D1_Rate);
              if(si_Price_Bars == WRONG_VALUE) { // handling of CopyRates function error
                if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError);
                return(EXIT_UNKNOWN);
              }
              
              if(e_Trade_Direction == ENTRY_BUY) {
                if(soa_Prev_D1_Rate[1].high < soa_Prev_D1_Rate[0].high) return(EXIT_NONE);        // noch kein Ausbruch
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: price broke-through yesterday’s High: %s > %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].high, _Digits), DoubleToString(soa_Prev_D1_Rate[0].high, _Digits));
                return(EXIT_BUY);
              } else {
                if(soa_Prev_D1_Rate[1].low > soa_Prev_D1_Rate[0].low) return(EXIT_NONE);          // noch kein Ausbruch
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: price broke through yesterday’s Low: %s < %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].low, _Digits), DoubleToString(soa_Prev_D1_Rate[0].low, _Digits));
                return(EXIT_SELL);
              }
            }
            
            return(EXIT_NONE); // für alle
  }
  
  return(EXIT_UNKNOWN);
  }

Hier haben wir eine Sicherung für den Fall, dass die Option "Trailing-Stop" gewählt wurde - die Funktion gibt das Fehlen eines Signals ohne weitere Analyse zurück. Für zwei weitere Optionen wird das Auftreten der Ereignisse 'Morgen ist gekommen' und das 'gestrige Extremum wurde durchgebrochen' programmiert. Die Varianten der von der Funktion zurückgegebenen Werte des Typs ENUM_EXIT_SIGNAL sind der entsprechenden Enumeration der Eingangssignalwerte (ENUM_ENTRY_SIGNAL) sehr ähnlich:

enum ENUM_EXIT_SIGNAL {  // Enum. der Schließsignale
  EXIT_UNKNOWN,          // unbekannt
  EXIT_BUY,              // Schließe Kauf
  EXIT_SELL,             // Schließe Verkauf
  EXIT_ALL,              // Schließe alles
  EXIT_NONE              // Schließe nichts
  };

Der Indikator für manuelles Handeln

Der oben beschriebene Signalteil sollte im Roboter für den automatisierten Handel eingesetzt werden. Lassen Sie uns diese Anwendungsmethode im Folgenden näher erläutern. Erstens, lassen Sie uns ein Werkzeug für eine explizitere Berücksichtigung der TS-Eigenheiten in den Charts im Terminal erstellen. Dies wird ein Indikator sein, der den Signalteil ohne weitere Änderungen anwendet und die berechneten Handelsniveaus anzeigt - den Preis des Auftrages und des Stop-Loss'. Das Schließen einer Position im Gewinn ist mit diesem Indikator nur durch eine vereinfachte Variante möglich - bei Erreichen des angegebenen Take-Profit. Wie Sie sich erinnern, gibt es im Signalteil kompliziertere Algorithmen für die Erkennung des Exitsignals, aber implementieren wir das im Roboter.

Zusätzlich zu den Handelsniveaus wird der Indikator die Balken der ersten Stunde des Tages markieren, um zu verdeutlichen, warum diese Levels verwendet werden. Eine solche Markierung wird helfen, die Vor- und Nachteile der meisten Regeln der Strategie des Momentum-Pinballs visuell zu bewerten - um Dinge zu entdecken, die nicht aus den Berichten der Strategietester gewonnen werden können. Die visuelle Analyse, die mit den Ergebnissen aus den Tests ergänzt werden, erleichtert es, Regeln der HS effizienter zu formulieren.

Um den Indikator im normalen manuellen Handel anwenden zu können, fügen wir für die Händler eine Hinweisgebung in Echtzeit hinzu. Diese Benachrichtigung enthält die empfohlene Handelsrichtung des Signalteils zusammen mit den Preisen für die Pending Order und den Stop-Loss. Es gibt drei Möglichkeiten der Benachrichtigung - ein normales Pop-up-Fenster mit Text- und Tonsignal, eine E-Mail-Nachricht und die Push-Benachrichtigung auf das Mobiltelefon.

Alle Anforderungen für den Indikator sind aufgelistet. Beginnen wir also alles zu programmieren. Um auf dem Chart alle beabsichtigten Objekte zu zeichnen sollte der Indikator folgendes haben: Einen Puffer des Typs DRAW_FILLING (die Balken der jew. ersten Stunde des Tages auszufüllen) und drei Puffer für Preise (Eröffnungspreis, Profit-Target, die Stop-Loss). Einer von ihnen (die Eröffnungspreise der Pending-Order) sollte eine Farbe (Typ DRAW_COLOR_LINE) je nach Handelsrichtung erhalten, die beiden anderen können dieselbe Farbe (Type DRAW_LINE) erhalten:

#property indicator_chart_window
#property indicator_buffers  6
#property indicator_plots    4
#property indicator_label1  “1st hour of a day"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C'255,208,234', C'179,217,255'
#property indicator_width1  1
#property indicator_label2  “Entry level"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_style2  STYLE_DASHDOT
#property indicator_color2  clrDodgerBlue, clrDeepPink
#property indicator_width2  2
#property indicator_label3  "Stop Loss"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_DASHDOTDOT
#property indicator_color3  clrCrimson
#property indicator_width3  1
#property indicator_label4  "Take Profit"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrGreen
  #property indicator_width4  1

Jetzt deklarieren wir die Enumerationen, von denen ein Teil im Indikator nicht benötigt wird (sie werden nur vom Expert Advisor verwendet), aber sie sind in den Funktionen des Signalteils eingebunden. Diese Variablen vom Typ Enum werden für die Arbeit mit der Protokollierung und verschiedenen Methoden des Schließens einer Position benötigt; sie werden im Indikator weglassen - erinnern wir uns, dass wir hier nur einen einfachen Take-Profit imitieren. Nach der Deklaration dieser Variablen folgen die globalen und externen Variablen für die Benutzereinstellungen:

enum ENUM_LOG_LEVEL {  // Log-Level
  LOG_LEVEL_NONE,      // keine Logs
  LOG_LEVEL_ERR,       // nur Informationen
  LOG_LEVEL_INFO,      // Fehler und Kommentare des Roboters
  LOG_LEVEL_DEBUG      // alles ohne Ausnahme
};
enum ENUM_ENTRY_SIGNAL {  // List der Eröffnungssignale
  ENTRY_BUY,              // Kaufsignal
  ENTRY_SELL,             // Verkaufssignal
  ENTRY_NONE,             // kein Signal
  ENTRY_UNKNOWN,          // undefiniert
  ENTRY_INTERNAL_ERROR    // interner Fehler
};
enum ENUM_EXIT_SIGNAL {  // Liste der Signale zum Schließen
  EXIT_UNKNOWN,          // unbekannt
  EXIT_BUY,              // Kauf schließen
  EXIT_SELL,             // Verkauf schließen
  EXIT_ALL,              // alles schließen
  EXIT_NONE              // nichts schließen
};
#include <Expert\Signal\Signal_Momentum_Pinball.mqh>     // Signalteil des ‘Momentum-Pinball’ HS
input uint    TS_MomPin_Take_Profit = 10;                // Momentum-Pinball: Take Profit (in Points)
input bool    Show_1st_H1_Bar = true;                    // Ersten H1-Balken hervorheben?
input bool    Alert_Popup = true;                        // Alert: Pop-up-Fenster anzeigen?
input bool    Alert_Email = false;                       // Alert: Email senden?
input string  Alert_Email_Subj = "";                     // Alert: Betreff der Email
input bool    Alert_Push = true;                         // Alert: Push-Benachrichtigung senden?
input uint  Days_Limit = 7;                              // Umfang der Historie (Kalendertage)
ENUM_LOG_LEVEL  Log_Level = LOG_LEVEL_DEBUG;             // Arte des Log
double
  buff_1st_H1_Bar[], buff_1st_H1_Bar_Zero[],             // Puffer der ersten H1-Balken
  buff_Entry[], buff_Entry_Color[],                      // Puffer der Pending-Order
  buff_SL[],                                             // Puffer der Stop-Loss
  buff_TP[],                                             // Puffer der Take-Profit
  gd_Entry_Offset = 0,                                   // TS_MomPin_Entry_Offset in Preisen des Symbols
  gd_Exit_Offset = 0                                     // TS_MomPin_Exit_Offset in Preisen des Symbols
  ;

Die Initialisierungsfunktion hat nichts Besonderes - hier weisen Sie den für diese Puffer deklarierten Arrays Indizes von Indikatorpuffern zu. Auch hier sollten wir die Benutzereinstellungen in Points in Symbolpreise umwandeln; dies sollte getan werden, um den CPU-Last zumindest ein wenig zu reduzieren und nicht tausende Male während der Laufzeit des Hauptprogramms eine solche Konvertierung durchzuführen:

int OnInit() {
  // Konvertierung von Points in Preise:
  gd_Entry_Offset = TS_MomPin_Entry_Offset * _Point;
  gd_Exit_Offset = TS_MomPin_Exit_Offset * _Point;
  
  // Zweck der Indikatorpuffer:
  
  // der erste H1-Balken 
  SetIndexBuffer(0, buff_1st_H1_Bar, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(1, buff_1st_H1_Bar_Zero, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
  
  // Linie der Pending-Order
  SetIndexBuffer(2, buff_Entry, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(3, buff_Entry_Color, INDICATOR_COLOR_INDEX);
  
  // Linie des SL
  SetIndexBuffer(4, buff_SL, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, 0);
  
  // Linie des TP
  SetIndexBuffer(5, buff_TP, INDICATOR_DATA);
    PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, 0);
  
  IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
  IndicatorSetString(INDICATOR_SHORTNAME, "Momentum-Pinball");
  
  return(INIT_SUCCEEDED);
  }

Im Indikatorcode des vorherigen Artikels dieser Serie wurde eine Programmstruktur geschaffen, deren Zweck darin besteht, Informationen jeglicher Art zwischen den Ticks zu speichern. Sie können dort nachlesen, warum sie benötigt wird und wie sie hier ausschaut. Wir werden es einfach ohne jegliche Modifikationen übernehmen. In dieser Indikatorversion wird nur der Merker (flag) für den Beginn eines neuen Balkens verwendet. Aber wenn Sie den Indikator für den manuellen Handel weiterentwickeln wollen, werden Ihnen andere Funktionen nützlich sein. Den vollständigen Code von go_Brownie finden Sie am Ende der Quellcodedatei des Indikators (TS_Momentum_Pinball.mq5), die diesem Artikel beigefügt ist. Dort ist auch der Code für die Push-Benachrichtigung f_Do_Alert zu sehen - es gibt auch keine Änderung gegenüber dem bisherigen Indikator dieser Artikelserie, so dass es nicht notwendig ist, ihn im Detail zu betrachten.

Innerhalb der Funktion OnCalculate() müssen vor Beginn der Programmhauptschleife die notwendigen Variablen deklariert werden. Wenn dies nicht der erste Aufruf der Hauptschleife ist, sollte die Bandbreite der Neuberechnung nur durch die aktuellen Balken begrenzt werden - für diese Handelsstrategie sind dies die Balken von gestern und heute. Handelt es sich um den ersten Schleifenaufruf nach der Initialisierung, müssen die restlichen Daten aus den Indikatorpuffern gelöscht werden. Geschieht dies nicht, so bleiben beim Umschalten des Zeitrahmens nicht aktuelle Bereiche gefüllt. Außerdem sollte der Aufruf der Hauptfunktion auf einmal pro Balken begrenzt werden. Es ist bequem, dies mit der go_Brownie zu tun:

go_Brownie.f_Update(prev_calculated, prev_calculated);     // Brownie mit Daten "füttern"
datetime t_Time = TimeCurrent();                           // letzte Serverzeit
int
  i_Period_Bar = 0,                                        // Hilfszähler
  i_Current_TF_Bar = 0                                     // Schleife mit dem Balkenindex
;
if(go_Brownie.b_First_Run) {                               // falls es der erste Aufruf ist
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 8640086400 * Days_Limit, t_Time);
  // clearing buffer at re-initialization:
  ArrayInitialize(buff_1st_H1_Bar, 0); ArrayInitialize(buff_1st_H1_Bar_Zero, 0);
  ArrayInitialize(buff_Entry, 0); ArrayInitialize(buff_Entry_Color, 0);
  ArrayInitialize(buff_TP, 0);
  ArrayInitialize(buff_SL, 0);
} else if(!go_Brownie.b_Is_New_Bar) return(rates_total);   // warten auf das Schließen des Balkens
else {                                                     // neuer Balken
  // minimum re-calculation depth - from day beginning:
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 86400, t_Time);
}
ENUM_ENTRY_SIGNAL e_Entry_Signal = ENTRY_UNKNOWN;          // Eröffnungssignal
double
  d_SL = WRONG_VALUE,                                      // Stop-LossS
  d_TP = WRONG_VALUE,                                      // Take-Profit
  d_Entry_Level = WRONG_VALUE,                             // Eröffnungspreis
  d_Range_High = WRONG_VALUE, d_Range_Low = WRONG_VALUE    // Grenzen des ersten Balkens
;
datetime
  t_Curr_D1_Bar = 0,                                       // aktueller D1-Balken (des Musters 2. Balken)
  t_Last_D1_Bar = 0,                                       // Zeit des letzten D1-Balkens mit gültigem Signal
  t_Entry_Bar = 0                                          // Zeitpunkt der Pending-Order
;
// sicherstellen, dass die Neuberechnung des Balkenindex innerhalb des Erlaubten geschieht:
  i_Current_TF_Bar = int(fmax(0, fmin(i_Current_TF_Bar, rates_total — 1)));

Nun lassen Sie uns die Hauptarbeitsschleife programmieren. Am Anfang jeder Iteration sollten wir Daten des Signalteils empfangen, das Vorhandensein von Fehlern und den Übergang zur nächsten Schleifeniteration festzulegen, wenn kein Signal verfügbar ist:

while(++i_Current_TF_Bar < rates_total && !IsStopped()) {                // iterieren über die aktuellen Balken
  // receiving data from signal module:
  e_Entry_Signal = fe_Get_Entry_Signal(-Time[i_Current_TF_Bar], d_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low);
  if(e_Entry_Signal == ENTRY_INTERNAL_ERROR) {                           // Fehler bei der Abfrage der Daten des externen Indikators
    // berechnen und zeichnen sollte beim nächsten Tick wiederholt werden:
    go_Brownie.f_Reset();
    return(rates_total);
  }
    if(e_Entry_Signal > 1) continue;                                       // kein aktives Signal bei diesem Balken

Wird ein Signal bei diesem Balken entdeckt und ein Eröffnungspreis zurückgegeben, wird als erstes Take-Profit berechnet:

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400; // Beginn des Tages dieses Balkens

Beginnen wir damit, die Balken der ersten Stunde des Tages zu füllen, die bei der Berechnung der Niveaus verwendet werden:

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400;            // Beginn des Tages des Balkens
if(t_Last_D1_Bar < t_Curr_D1_Bar) {                                                 // dies ist der erste Balken des Tages mit einem gültigen Signal
    t_Entry_Bar = Time[i_Current_TF_Bar];                                             // Sichern des Eröffnungszeitpunktes

Zeichnen wir jetzt den Hintergrund des ersten Balken des Tages, die bei der Berechnung der Level verwendet werden:

// Hintergrund des 1. Balken des Tages zeichnen :
if(Show_1st_H1_Bar) {
  i_Period_Bar = i_Current_TF_Bar;
  while(Time[--i_Period_Bar] >= t_Curr_D1_Bar && i_Period_Bar > 0)
    if(e_Entry_Signal == ENTRY_BUY) {                               // Kaufmuster
      
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_Low;
    } else {                                                        // Verkaufsmuster
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_Low;
    }
  }

Zeichnen wir dann die Zeile zum Festlegen der Pending-Order bis zu dem Moment, zu dem die Pending-Order zu einer offenen Position wird, d.h. wenn Sie den Preis dieses Niveaus erreicht:

// Entry line till crossed by a bar:
i_Period_Bar = i_Current_TF_Bar - 1;
if(e_Entry_Signal == ENTRY_BUY) {                               // Kauf-Muster
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {            // Tagesende
      e_Entry_Signal = ENTRY_NONE;                              // Pending-Order bleibt offen
      break;
    }
    
    // Lnienlänge:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 0;
    
    if(d_Entry_Level <= High[i_Period_Bar]) break;               // Eröffnung während dieses Balkens
  }
} else {                                                         // Verkaufs-Muster
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {             // Tagesende
      e_Entry_Signal = ENTRY_NONE;                               // Pending-Order bleibt offen
      break;
    }
    
    // extend line:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 1;
    
    if(d_Entry_Level >= Low[i_Period_Bar]) break;               // Eröffnung während dieses Balkens
  }
  }

Wenn der Preis das berechnete Niveau vor dem Ende des Tages nicht erreicht hat, fahren Sie mit dem nächsten Schritt der Hauptschleife fort:

if(e_Entry_Signal == ENTRY_NONE) {                 // Pending-Order bleibt bis zum Tagesende offen
  i_Current_TF_Bar = i_Period_Bar;                 // diese D1-Balken interessieren nicht mehr
  continue;
  }

Wenn dieser Tag noch nicht abgeschlossen ist und die Zukunft der offenen Pending-Order noch nicht bestimmt ist, hat es keinen Sinn, die Hauptschleife fortzusetzen:

if(i_Period_Bar >= rates_total - 1) break;        // der aktuelle Tage hat sich beendet

Nach diesen beiden Filtern gibt es nur ein mögliches Szenario - die Pending-Order wurde ausgelöst. Wir müssen den Balken finden, während dem die Pending-Order ausgelöst wurde und, ausgehend von dieser Bar, die Take-Profit und Stop-Loss zeichnen bis die Position geschlossen ist. In diesem Fall ist es notwendig, eine Situation zu schaffen, in der das Öffnen und Schließen der Position auf demselben Balken stattfindet - in diesem Fall muss die Linie um einen Balken in die Vergangenheit zu verlängert werden, so dass es auf dem Chart zu sehen ist:

// Order wurde ausgelöst, finde den richtigen Balken:
i_Period_Bar = fmin(i_Period_Bar, rates_total - 1);
buff_SL[i_Period_Bar] = d_SL;
while(++i_Period_Bar < rates_total) {
  if(TS_MomPin_Exit_Mode == CLOSE_ON_SL_TRAIL) {
    if(Time[i_Period_Bar] >= t_Curr_D1_Bar + 86400) break;        // das ist der Balken des nächsten Tages
    
    // Lines TP and SL until the bar crossing one of them:
    buff_SL[i_Period_Bar] = d_SL;
    buff_TP[i_Period_Bar] = d_TP;
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_SL >= Low[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL <= High[i_Period_Bar]
    )) {                                                          // SL exit
      if(buff_SL[int(fmax(0, i_Period_Bar - 1))] == 0.) {
   // beginning and end on a single bar, extend it by 1 bar to the past
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_TP <= High[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL >= Low[i_Period_Bar]
    )) {                                                         // TP exit
      if(buff_TP[int(fmax(0, i_Period_Bar - 1))] == 0.) {
        // beginning and end on a single bar, extend it by 1 bar to the past
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
  }
  }

Nach dem Schließen der Position können die verbleibenden Balken des Tages in der Hauptschleife übersprungen werden:

i_Period_Bar = i_Current_TF_Bar;
t_Curr_D1_Bar = Time[i_Period_Bar] - Time[i_Period_Bar] % 86400;
while(
  ++i_Period_Bar < rates_total
  &&
  t_Curr_D1_Bar == Time[i_Period_Bar] - Time[i_Period_Bar] % 86400
  ) i_Current_TF_Bar = i_Period_Bar;

Dies ist der Rest der Hauptschleife. Es bleibt noch die Hinweisgebung zu erstellen, wenn das Signal auf dem aktuellen Balken erkannt wird:

i_Period_Bar = rates_total - 1;                                            // aktueller Balken
if(Alert_Popup + Alert_Email + Alert_Push == 0) return(rates_total);       // alles deaktiviert
if(t_Entry_Bar != Time[i_Period_Bar]) return(rates_total);                 // kein Signal während dieses Balkens
// Nachrichtentext:
string s_Message = StringFormat("ТС Momentum-Pinball: needed %s @ %s, SL: %s",
  e_Entry_Signal == ENTRY_BUY ? "BuyStop" : "SellStop",
  DoubleToString(d_Entry_Level, _Digits),
  DoubleToString(d_SL, _Digits)
);
// Hinweis:
  f_Do_Alert(s_Message, Alert_Popup, false, Alert_Email, Alert_Push, Alert_Email_Subj);

Der vollständige Indikatorcode ist in der unten angehängten Datei TS_Momentum_Pinball.mq5 enthalten.

Expert Advisor zum Testen des Momentum-Pinball HS

Die Eigenschaften des Basic Expert Advisors sollten etwas erweitert werden, wenn auch andere Handelsstrategien aus dem Buch von Raschke & Connors testen will. Den Quellcode, der dieser Version zugrunde liegt, finden Sie zusammen mit einer ausführlichen Beschreibung im vorherigen Artikel. Wir wiederholen das hier nicht, sondern beschränken uns auf die zwei substantiellen Änderungen und Ergänzungen.

Die erste Ergänzung - die Enumeration der Signale zum Schließen der Position, die in der vorherigen Version des Handelsroboters noch nicht existiert. Zusätzlich wurde die Enumeration der Eröffnungssignale um ENTRY_INTERNAL_ERROR erweitert. Diese Enumerationen unterscheiden sich nicht von den gleichen im oben besprochenen Indikator. Im Code des Roboters platzieren wir sie vor dem Laden der Klasse mit den Handelsoperationen aus der Standardbibliothek. In der Datei Street_Smarts_Bot_Bot_MomPin.mq5 im Anhangs des Artikels sind dies die Zeilen 24...32.

Die zweite Änderung hängt damit zusammen, dass der Signalteil nun auch die Signale zum Schließen einer Position liefert. Fügen wir einen entsprechenden Codeblock hinzu, um auch mit diesem Signal zu arbeiten. In der vorherigen Roboterversion gibt es ein "if" für die Überprüfung, ob die vorhandene Position neu ist (Zeile 139); die Überprüfung wird zur Berechnung und Platzierung des ersten Stop-Loss verwendet. In dieser Version fügen wir dem 'if' durch 'else' einen entsprechenden Codeblock für den Aufruf des Signalteils hinzu. Wenn das entsprechende Signal erscheint, sollte der Expert Advisor seine Position schließen:

} else {                       // keine neue Position
                               // Bedingung für das Schließen erfüllt?
  ENUM_EXIT_SIGNAL e_Exit_Signal = fe_Get_Exit_Signal(d_Entry_Level, datetime(PositionGetInteger(POSITION_TIME)), e_Entry_Signal, TimeCurrent(), TS_MomPin_Exit_Mode);
  if((
      e_Exit_Signal == EXIT_BUY && e_Entry_Signal == ENTRY_BUY
    ) || (
      e_Exit_Signal == EXIT_SELL && e_Entry_Signal == ENTRY_SELL
    ) || e_Exit_Signal == EXIT_ALL
  ) {
                              // Schließen, jetzt!
    CTrade o_Trade;
    o_Trade.LogLevel(LOG_LEVEL_ERRORS);
    o_Trade.PositionClose(_Symbol);
    return;
  }
  }

Im Quellcode des Roboters sind dies die Zeilen 171..186.

Es gibt einige Änderungen im Code der Funktion, die den Abstand zu den Handelsebenen fb_Is_Acceptable_Distance (Zeilen 424..434) steuert.


Backtesting der Strategie

Wir haben ein paar Werkzeuge (Indikator und Berater) für die Tests eines Handelssystems geschaffen, die durch das Buch von L. Rashke und L. Konnors berühmt wurde. Das Hauptziel des Backtestings eines Expert Advisor mit historischen Daten ist, die Effizienz des Handelsroboters, eines dieser Werkzeuge, zu überprüfen. Daher habe ich die Parameter nicht optimiert, der Test wurde mit den Standardeinstellungen durchgeführt.

Die vollständigen Ergebnisse aller Testdurchläufe können im angehängten Archiv gefunden werden, aber hier werde ich nur die Graphen der Saldenänderung geben. Nur als Illustration für den zweitwichtigsten Zweck des Testens - eine grobe (keine optimierten Parameter) Bewertung der Leistung der HS in einem aktuellen Markt. Ich erinnere daran, dass die Autoren die Strategie mit Kursen des Endes des letzten Jahrhunderts illustriert haben.

Der Zeitplan für die Änderung des Saldos beim Test des Expert Advisors seit Anfang 2014 mit den Kursen des Demo-Servers MetaQuotes. Symbol — EURJPY, Zeitrahmen — H1:


Hier für das Symbol EURUSD, gleichem Zeitrahmen und dieselbe Zeitspanne für den Backtest:


Der Backtest ohne Änderung der Einstellungen mit Gold (XAUUSD) über den gleichen Zeitraum und mit dem gleichen Zeitrahmen schaut dann so aus:


Schlussfolgerung

Die Regeln für das Handelssystem Momentum-Pinball, das in Street Smarts: High Probability Short-Term Trading Strategies ausgearbeitet sind, werden in den Code des Indikators und des Expert Advisor übernommen. Leider ist die Beschreibung nicht so detailliert wie sie sein könnte und bietet mehr als eine Variante für die Regeln der Positionsverfolgung und -schließung. Daher gibt es für diejenigen, die die Besonderheiten des Handelssystems im Detail erforschen wollen, ein ziemlich weites Feld für die Auswahl optimaler Parameter und Algorithmen. Der erzeugte Code ermöglicht es, dies zu tun; außerdem werden Quellcodes hoffentlich nützlich sein, um objektorientierte Programmierung zu studieren.

Die Quellcodes die kompilierten Dateien und die Bibliotheken befinden sich in der Datei MQL5.zip in den entsprechenden Verzeichnissen. Der Zweck jeder Datei ist:

# Dateiname Typ  Beschreibung 
 1  LBR_RSI.mq5  Indikator Indikator, der ROC und RSI zusammenführt. Zur Bestimmung der Handelsrichtung (oder Handelsverbot) des aktuellen Tages
 2  TS_Momentum_Pinball.mq5  Indikator Indikator für das manuelle Handeln dieser HS. Zeigt die Preise zum Eröffnen und Schließen, hebt den ersten H1-Balken hervor, auf dem die Berechnungen basieren
 3   Signal_Momentum_Pinball.mqh  Bibliothek  Bibliothek mit den Funktion, Strukturen und den Einstellungen. Verwendet vom Indikator und dem Expert Advisor
 4  Street_Smarts_Bot_MomPin.mq5   Expert Advisor Expert Advisor für den automatisierten Handel dieser HS