English Русский 中文 Español 日本語 Português
Die Trading-Strategie '80-20'

Die Trading-Strategie '80-20'

MetaTrader 5Handelssysteme | 9 Dezember 2016, 12:12
2 025 2
Alexander Puzanov
Alexander Puzanov

Einführung

'80-20' — der Name einer Traiding-Strategie (TS), der im Buch Street Smarts: High Probability Short-Term Trading Strategies Linda Raschke und Laurence Connors beschrieben wurde. Genauso wie einige andere Strategien, die in meinem vorherigen Artikel betrachtet wurden, haben Autoren diese Strategie für die Test-Stufe des Preis-Bereiches zugeordnet. Sie ist auch orientiert, um den Gewinn aus einem fehlerhaften Durchbruch zu ziehen oder aus einem Rollback von der Grenze. Allerdings dieses Mal das Signal zu finden, wird die Entwicklung des Preises auf der deutlich kürzeren Periode der History analysiert - nur für den vorherigen Tag. Die Lebensdauer des empfangenen Signals ist auch relativ niedrig - das System ist für ein Intraday-Handeln vorgesehen.

Der erste Zweck dieses Artikels ist - die Erstellung eines Signal-Moduls in MQL5 zu beschreiben, die die Regel der Handelsstrategie '80 -20 ' realisiert. Wir verbinden dann dieses Modul zum Basis-EA, der im vorhergehenden Artikel in dieser Reihe erstellt wurde, aber wir bearbeiten es ein wenig. Außerdem werden wir dieses Modul unverändert bei der Erstellung eines Indikators verwenden, der für ein manuelles Handeln verwendet wird.

Ich erinnere daran, dass es in dieser Reihe von Artikeln der Code für diese Kategorie der Programmierer orientiert ist, die als "leicht fortgeschrittene Anfänger" definiert werden können. Deshalb hilft der Code neben den Hauptaufgaben noch beim Übergang von der prozeduralen Programmierung zur objektorientierten Programmierung. Darin werden keine Klassen erstellt, aber rs werden in vollem Umfang die einfacheren Analoge eingesetzt - Strukturen.

Ein weiterer Zweck des Artikels - die Werkzeuge zu erstellen, die überprüfen, ob diese Strategie heute relevant ist, denn bei ihrer Erstellung gingen Raschke und Connors vom Verhalten des Marktes dieser Zeit (Ende des letzten Jahrhunderts) aus. Mehrere Tests des erstellten EAs auf den aktuellen History-Daten werden am Ende des Artikels präsentiert.


Die Trading-Strategie '80-20'

Als theoretische Begründung berufen sich die Autoren auf das Buch The Taylor Trading Technique von George Taylor, sowie auf Werke der Computer-Analyse von Futures-Märkten von Steve Moore und auf die praktische Erfahrung des Händlers Derek Gipson. In der Grundlage gesteckte Hypothese der TS kann man kurz folgendermaßen formuliert werden: Wenn der Eröffnungs- und Schlusskurs von gestern gegenüber dem Bereich des intraday Trading-Bereich verteilt sind, dann heute kann man mit hoher Wahrscheinlichkeit eine Wende in Richtung der Öffnung von gestern erwarten. Es ist wichtig, damit die gestrigen Eröffnungs- und Schlusskurse sich nahe genug zu den Grenzen des Bereichs befinden, und die Wende würde eben heute beginnen, und nicht vorm Schluss der gestrigen Bar. Der durch seine eigenen Autoren-Korrekturen ergänzte Satz von Regeln TS '80-20', für den Kauf kann man folgendermaßen formulieren:

1. Stellen Sie sicher, dass der Markt gestern in dem oberen 20% des Tagesbereiches geöffnet wurde und geschlossen in dem unteren 20% des Bereiches

2. Warten Sie ab, bis das heutige Preis-Minimum das gestrige Minimum mindestens um 5 Ticks durchbricht

3. Setzen Sie noch einen Pending Order zum Kauf auf der unteren Grenze des gestrigen Bereiches

4. Unmittelbar nach dem Auslösen eines Pending Orders setzen Sie seinen anfangs Stoploss beim Minimum dieses Tages

5. Verwenden Sie einen Trailing Stop, um Gewinne zu schützen

Die Regeln für die Eintritte für Verkauf sind ähnlich, aber die gestrige Bar muss bullish sein, den Ordner zum Kauf muss auf der oberen Grenze des Bars platziert werden und Stoploss auf dem Level des heutigen Maximums setzen.

Ein weiteres wichtiges Detail erscheint im Buch bei der Illustration der TS am Chart aus der History - die Autoren geben Acht auf die Größe des geschlossenen Tagesbars. Laut Linda Raschke sollte es groß genug sein - größer als die durchschnittliche Größe der täglichen Bars. Aber sie sagt nicht genau, wie viele Tage der History man in Betracht nehmen sollte, wenn ein durchschnittlicher Tagesbereich berechnet werden muss.

Und vergessen Sie nicht, dass die TS ausschließlich für das Intraday-Handeln vorgesehen ist - im Buch gezeigte Beispiele werden in Charts mit den 15-Minuten-Timeframes benutzt.

Der Signal-Block und der Indikator, der den Block benutzt, werden unten beschrieben, auch macht dieser Indikator Markierungen über dieser TS. Im Folgenden sehen Sie ein paar Screenshots mit dem Ergebnis des Indikators. Auf ihnen ist es deutlich die Muster zu sehen, die den Regeln des Systems entsprechen, und die Handels-Ebene, die zu den Mustern verknüpft sind.

Fünf-Minuten-Timeframes:


Das Ergebnis der Analyse dieses Musters sollte die Installation eines Pending Orders zum Kauf sein. Die relevante Handels-Ebene sind besser auf einer Minute-Timeframe zu sehen:


Ein ähnliches Muster mit der entgegengesetzten Richtung des Handels in der Fünf-Minuten-Timeframe:

Das Muster über der 80-20

Seine Handels-Ebene (eine Minute-Timeframe):


 

Signal-Modul

Um ein Beispiel zu zeigen, wie zusätzliche Optionen zu einer Autoren-TS hinzugefügt wird, fügen wir die Berechnung der Take-Profit-Ebene hinzu. In der ursprünglichen Version gibt es diese Ebene nicht, um die Position zu schließen, wird nur die Ebene Stop Loss benutzt. Take Profit machen wir abhängig vom Benutzer definierten Wert des Durchbruchs (TS_8020_Extremum_Break) — multiplizieren es um den Benutzer definierten Koeffizient TS_8020_Take_Profit_Ratio.

Von der Hauptfunktion des Signalmoduls fe_Get_Entry_Signal benötigen wir: Der aktuelle Status des Signals, die berechneten Ebene des an Ein- und Ausgangs (Stop Loss und Take Profit) sowie die Grenzen des gestrigen Bereiches. Alle Ebene erhalten wir über die Links, die Funktionen auf Variablen hinweisen, und der zurückgegebene Signal-Status wird als eine Liste aus dem vorherigen Artikel verwendet:

enum ENUM_ENTRY_SIGNAL {  // Die Liste der Signale zum Eingang
  ENTRY_BUY,               // Signal für Kauf
  ENTRY_SELL,              // Signal für Verkauf
  ENTRY_NONE,             // kein Signal
  ENTRY_UNKNOWN           // Der Status ist nicht bestimmt worden
};

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal( // Die Analyse des zweikerzigen Musters D1
  datetime  t_Time,           // die laufende Zeit
  double&    d_Entry_Level,  //die Ebene des Eingangs (der Link zur Variable)
  double&    d_SL,           // die Ebene StopLoss (der Link zur Variable)
  double&    d_TP,           // die Ebene TakeProfit (der Link zur Variable)
  double&    d_Range_High,   // das Maximum des Bereiches 1-e Bar des Musters (der Link zur Variable)
  double&    d_Range_Low     // das Minimum des Bereiches 1-e Bar des Musters (der Link zur Variable)
) {}

Für die Berechnung des Signals muss man zwei letzten Bars der Tages-Timeframe analysieren. Wir werden mit dem ersten von ihnen beginnen — wenn er den Kriterien der TS nicht entspricht, dann ist es nicht nötig, die zweite Bar zu prüfen. Die Kriterien sind zwei:

1. Das Ausmaß der Bar (die Verschiedenheit der Preise High und Low) soll grösser sein, als der Mittelwert für die letzten XX Tage (dies wird durch die Benutzer-Einstellungen TS_8020_D1_Average_Period gegeben)

2. Die Ebene der Eröffnung und der Schließung der Bar sollen sich zu den entgegengesetzten Bars 20 % des Bereiches verhalten

Wenn diese Bedingungen erfüllt werden, so muss man sich für die weitere Nutzung die Preise High und Low merken. Und da sich die Parameter der ersten Bar des Musters im Laufe des ganzen Tages nicht ändern werden, werden wir sie bei jedem Aufruf der Funktion nicht überprüfen, sondern werden wir sie in der statischen Variablen speichern:

//Benutzer-Einstellungen
input uint  TS_8020_D1_Average_Period = 20;  // 80-20: Die Anzahl der Tage für die Berechnung des Mittelwertes vom Tagesbereich
input uint  TS_8020_Extremum_Break = 50 ;     // 80-20: Min. der Durchbruch des gestrigen Extremes (in Punkten)


static ENUM_ENTRY_SIGNAL se_Possible_Signal = ENTRY_UNKNOWN; // Die Richtung des Signals über die 1-e Bar des Musters
static double
  // Die Variablen für die Speicherung der berechneten Ebene zwischen Ticks
  sd_Entry_Level = 0,
  sd_SL = 0, sd_TP = 0 ,
  sd_Range_High = 0, sd_Range_Low = 0
;


// Die Überprüfung der 1-en Bar des Musters auf D1:
if(se_Possible_Signal == ENTRY_UNKNOWN) { // es wurde heute noch nicht durchgeführt
  st_Last_D1_Bar = t_Curr_D1_Bar; // An diesem Tag wird die 1-te Bar sich nicht ändern
  
  // Der durchschnittliche Tagesbereich
  double d_Average_Bar_Range = fd_Average_Bar_Range(TS_8020_D1_Average_Period, PERIOD_D1, t_Time);
  
  if(ma_Rates[0 ].high — ma_Rates[0].low <= d_Average_Bar_Range) {
    // Die 1-te Bar ist nicht groß genug
    se_Possible_Signal = ENTRY_NONE; // Das heißt, es wird heute kein Signal mehr
    return(se_Possible_Signal);
  }
  
  double d_20_Percents = 0.2 * (ma_Rates[0].high — ma_Rates[0].low); // 20% des gestrigen Bereichs
  if((
      // Die bärische Bar:
      ma_Rates[0].open > ma_Rates[0].high — d_20_Percents // Die Bar wurde in den oberen 20%geöffnet
      &&
      ma_Rates[0].close < ma_Rates[0].low + d_20_Percents // und geschlossen in den unteren 20%
    ) || (
      // bullish:
      ma_Rates[0].close > ma_Rates[0].high — d_20_Percents //Die Bar wurde in den unteren 20% geschlossen
      &&
      ma_Rates[0].open < ma_Rates[0].low + d_20_Percents // und geöffnet in den unteren 20%
  )) {
    // Die 1-te Bar entspricht den Bestimmungs-Bedingungen
    // der Richtung des Tages-Handelns über die 1-e Bar des Musters:
     se_Possible_Signal = ma_Rates[0].open > ma_Rates[0].close ? ENTRY_BUY : ENTRY_SELL;
    // Die Ebene des Markteintritts:
    sd_Entry_Level = d_Entry_Level = se_Possible_Signal == ENTRY_BUY ? ma_Rates[0].low : ma_Rates[0].high;
    // Die Grenzen des Bereiches 1-e Bar des Musters:
    sd_Range_High = d_Range_High = ma_Rates[0].high;
    sd_Range_Low = d_Range_Low = ma_Rates[0].low;
  } else {
    // Die Ebene der Eröffnung und der Schließung der 1-en Bar entsprechen nicht den Bestimmungs-Bedingungen
    se_Possible_Signal = ENTRY_NONE; // Das heißt, es wird heute kein Signal mehr
    return(se_Possible_Signal);
  }
}

Die Auflistung der Bestimmungs-Funktion des durchnitlichen Bereiches der Bar über den gegebenen Timeframe-Bars, ab der Zeit, die in der Funktion gegeben wurde:

double fd_Average_Bar_Range(    // Die Berechnung der durchschnittlichen Größe der Bar
  int i_Bars_Limit,             // Wie viele Bars sollten betrachtet werden
  ENUM_TIMEFRAMES e_TF = PERIOD_CURRENT,  // Die Timeframe der Bars
  datetime t_Time = WRONG_VALUE  // ab welcher Zeit soll man die Berechnung anfangen
) {
   double d_Average_Range = 0; // Die Variable für die Summierung der Werte
  if(i_Bars_Limit < 1) return(d_Average_Range);
  
  MqlRates ma_Rates[]; // Das Array für die Information über Bars
  
  // Die Informationserhaltung über die Bars vom eingegebenen Intervall der History:
  if(t_Time == WRONG_VALUE) t_Time = TimeCurrent();
  int i_Price_Bars = CopyRates(_Symbol , e_TF, t_Time, i_Bars_Limit, ma_Rates);
  
  if(i_Price_Bars == WRONG_VALUE) { // Die Bearbeitung des Fehlers der Funktion CopyRates
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: ошибка #%u" , __FUNCTION__, _LastError);
    return(d_Average_Range);
  }
  
  if(i_Price_Bars < i_Bars_Limit) { // Die Funktion CopyRates hat die Daten nicht vollständig rausgenommen
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: wurden %u Bars aus %u" kopiert, __FUNCTION__, i_Price_Bars, i_Bars_Limit);
  }
  
  // Die Summe der Bereiche:
  int i_Bar = i_Price_Bars;
  while(i_Bar-- > 0)
    d_Average_Range += ma_Rates[i_Bar].high — ma_Rates[i_Bar].low;
  
  // Der Mittelwert:
  return(d_Average_Range / double(i_Price_Bars));
}

Für die zweite (laufenden) Bar des Musters gibt es nur ein Kriterium - der Durchbruch der Grenzen des gestrigen Bereiches darf nicht weniger als es in Einstellungen ist(TS_8020_Extremum_Break). Soweit die Ebene erreicht wurde, erscheint das Signal für die Setzung des Pending Orders:

// Die Überprüfung der 2-en (laufenden) Bar des Musters auf D1:
if (se_Possible_Signal == ENTRY_BUY) {
  sd_SL = d_SL = ma_Rates[1].low; // StopLoss — auf das Maximum des heutigen Preises
  if(TS_8020_Take_Profit_Ratio > 0) sd_TP = d_TP = d_Entry_Level + _Point * TS_8020_Extremum_Break * TS_8020_Take_Profit_Ratio; // TakeProfit
  return(
    // Ist der Durchbruch nach unten auffällig genug?
     ma_Rates[1].close < ma_Rates[0].low — _Point * TS_8020_Extremum_Break ?
    ENTRY_BUY : ENTRY_NONE
  );
}

if(se_Possible_Signal == ENTRY_SELL) {
  sd_SL = d_SL = ma_Rates[1].high; // StopLoss — auf das Minimum des heutigen Preises
  if(TS_8020_Take_Profit_Ratio > 0) sd_TP = d_TP = d_Entry_Level — _Point * TS_8020_Extremum_Break * TS_8020_Take_Profit_Ratio; // TakeProfit
  return(
    // Ist der Durchbruch nach oben auffällig genug?
    ma_Rates[1].close > ma_Rates[0].high + _Point * TS_8020_Extremum_Break ?
    ENTRY_SELL : ENTRY_NONE
  );
}

Die oben beschriebenen Funktionen (fe_Get_Entry_Signal und fd_Average_Bar_Range) und die Benutzer-Einstellungen, die bei der Erhaltung des Signals benutzt werden, speichern wir in der Datei mqh. Eine vollständige Liste gibt es im Anhang zu diesem Artikel. Nennen wir die Datei Signal_80-20.mqh und ordnen wir sie im entsprechenden Ordner (MQL5\Include\Expert\Signal) des Daten-Kataloges des Terminals. 


Der Indikator für das manuelle Handeln

Der Indikator, genauso wie der EA, wird das oben beschriebene Signal-Modul. Er (Indikator) muss den Trader über die Signal-Erhaltung auf die Setzung des Pending Orders informieren und über die Berechnungs-Ebene - die Ebenen der Setzung des Orders, die Ebene Take Profit und Stop Loss. Der Benutzer kann die Methoden der Benachrichtigung selbst wählen - das kann ein standardmäßiges auftauchendes Fenster sein, die Benachrichtigung auf E-Mail oder die Benachrichtigung aufs mobile Gerät. Wählen kann man alles sofort oder eine beliebige bequeme Kombination der aufgezählten Optionen.

Der andere Zweck des Indikators — ist die Markierung auf der History des Handels nach der TS '80-20'. Er wird die Tagesbars einblenden, die den Kriterien des Systems entsprechen, und die Berechnungs-Handelsebene zeichnen. Nach den Linien der Ebene kann man bewerten, wie sich die Situation in der Zeit entwickelte. Für die Anschaulichkeit werden wir so machen: bei der Berührung vom Preis der Signallinie wird sie zu Ende gehen, es wird die Linie des Pending Orders anfangen, und bei der Auslösung des Pending Orders wird seine Linie zu Ende gehen und werden die Linien Take Profit und Stop Loss anfangen. Diese Linien werden unterbrochen, wenn der Preis einen von ihnen betreffen wird (der Order wird geschlossen). Bei solcher Markierung wird es einfacher sein, die Effektivität der Regeln des Handelssystems zu bewerten und zu verstehen, was man verbessern kann.

Wir werden mit der Erklärung der Puffer und der Parameter ihrer Abbildung beginnen. Erstens müssen wir zwei Puffer mit der Ausfüllung des vertikalen Gebietes (DRAW_FILLING) erklären. Einer wird den vollen Bereich der Tagesbar vom vorhergehenden Tag leuchten, der andere — nur das innere Bereiche, um sie von eingesetzten unteren und oberen 20% des Bereiches abzutrennen. Dann werden wir zwei Puffer für vielfarbige Signallinien und die Pending Order (DRAW_COLOR_LINE) erklären. Ihre Farbe wird von der Richtung des Handelns abhängen. Und noch zwei Linien ( Take Proft und Stop Loss) werden die Farben (DRAW_LINE) nicht ändern — sie werden die standardmäßigen Farben verwenden, die ihnen im Terminal abgeführt sind. Alle gewählten Arten der Abbildung, außer der einfachen Linie, fordern zwei Puffer, deshalb wird der Code so aussehen:

#property indicator_chart_window
#property indicator_buffers  10
#property indicator_plots    6

#property indicator_label1  "die 1-e Bar des Musters"
#property indicator_type1    DRAW_FILLING
#property indicator_color1  clrDeepPink, clrDodgerBlue
#property indicator_width1  1

#property indicator_label2  "1й бар паттерна"
#property indicator_type2   DRAW_FILLING
#property indicator_color2   clrDeepPink, clrDodgerBlue
#property indicator_width2  1

#property indicator_label3  "Die Signal-Ebene"
#property indicator_type3   DRAW_COLOR_LINE
#property indicator_style3  STYLE_SOLID
#property indicator_color3  clrDeepPink, clrDodgerBlue
#property indicator_width3  2

#property indicator_label4   "die Ebene des Eintritts"
#property indicator_type4   DRAW_COLOR_LINE
#property indicator_style4  STYLE_DASHDOT
#property indicator_color4  clrDeepPink, clrDodgerBlue
#property indicator_width4  2

#property indicator_label5  "Stop Loss"
#property indicator_type5   DRAW_LINE
#property indicator_style5   STYLE_DASHDOTDOT
#property indicator_color5  clrCrimson
#property indicator_width5  1

#property indicator_label6  "Take Profit"
#property indicator_type6   DRAW_LINE
#property indicator_style6  STYLE_DASHDOTDOT
#property indicator_color6   clrLime
#property indicator_width6  1

Wir werden dem Benutzer die Möglichkeit anbieten, die Ausfüllung der ersten Bar des Tages-Musters abzuschalten, die Optionen der Benachrichtigung über das Signal zu wählen und die Tiefe der Markierung der History zu beschränken. Hierher werden wir auch alle Einstellungen des Handelssystems aus dem Signalmodul aufnehmen. Dazu muss man vorläufig die im Modul eingesetzten Variable aufzuzählen, selbst wenn einige von ihnen nur im EA verwendet werden und sie sind im Indikator nicht nötig:

#include <Expert\Signal\Signal_80-20.mqh> // Das Signal-Modul TS '80-20'

input bool    Show_Outer = true;      // Die 1-e Bar des Musters: Soll der ganze Bereich gezeigt werden?
input bool    Show_Inner = true;       // Die 1-e Bar des Musters: Soll der innere Bereich gezeigt werden?
input bool    Alert_Popup = true;      // Warnung: Sollen die auftauchenden Fenster gezeigt werden?
input bool    Alert_Email = false;    // Warnung: Soll eMail geschickt werden?
input string   Alert_Email_Subj = "";  // Warnung: Das Thema der eMail-Nachricht
input bool     Alert_Push = true;      // Warnung: Soll push-Benachrichtigung geschickt werden?

input uint  Bars_Limit = 2000;  // Die Tiefe der Markierungen der History (in Bars der TF)



ENUM_LOG_LEVEL  Log_Level = LOG_LEVEL_NONE;  // Der Protokoll-Modus
double
  buff_1st_Bar_Outer[], buff_1st_Bar_Outer_Zero[], // Die Puffer für das Zeichen des ganzen Bereiches 1-e Bar des Musters
  buff_1st_Bar_Inner[], buff_1st_Bar_Inner_Zero[], // Die Puffer für das Zeichen des inneren 60% Bereiches 1-e Bar des Musters
  buff_Signal[], buff_Signal_Color[], // Die Puffer der Signal-Linie
  buff_Entry[], buff_Entry_Color[], // Die Puffer der Linie des Pending-Orders
  buff_SL[], buff_TP[], // Die Puffer der Linien StopLoss und TakeProfit
  gd_Extremum_Break = 0 // TS_8020_Extremum_Break in Preisen des Werkzeugs
;
int
  gi_D1_Average_Period = 1, // Der korrekte Wert für TS_8020_D1_Average_Period
  gi_Min_Bars = WRONG_VALUE // Die minimale nötige Anzahl der Bars für die Neuberechnung
;



int OnInit() {
  // Die Überprüfung des eingegebenen Parameters TS_8020_D1_Average_Period:
  gi_D1_Average_Period = int(fmin(1, TS_8020_D1_Average_Period));
  // Der Umtausch der Punkten in Preisen des Werkzeugs:
  gd_Extremum_Break = TS_8020_Extremum_Break * _Point ;
  // Die minimale nötige Anzahl der Bars für die Neuberechnung = der Anzahl der Bars TF in Tagen
  gi_Min_Bars = int(86400 / PeriodSeconds());
  
  // Der Zweck der Puffer des Indikators:
  
  // Das Rechteck des vollständigen Bereiches der 1-en Bar
  SetIndexBuffer(0, buff_1st_Bar_Outer, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(1, buff_1st_Bar_Outer_Zero, INDICATOR_DATA);
  
  // Das Rechteck des inneren Bereiches der 1-en Bar
  SetIndexBuffer(2, buff_1st_Bar_Inner, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
   SetIndexBuffer(3, buff_1st_Bar_Inner_Zero, INDICATOR_DATA);
  
  // die Signal-Linie
   SetIndexBuffer(4, buff_Signal, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(5, buff_Signal_Color, INDICATOR_COLOR_INDEX);
  
  // Die Linie der Setzung des Pending-Orders
  SetIndexBuffer(6, buff_Entry, INDICATOR_DATA);
    PlotIndexSetDouble(3, PLOT_EMPTY_VALUE , 0);
  SetIndexBuffer(7, buff_Entry_Color, INDICATOR_COLOR_INDEX);
  
  // Die Linie SL
  SetIndexBuffer(8, buff_SL, INDICATOR_DATA);
    PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, 0);
  
  // Die Linie TP
  SetIndexBuffer(9, buff_TP, INDICATOR_DATA);
    PlotIndexSetDouble(5, PLOT_EMPTY_VALUE, 0);
  
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
  IndicatorSetString(INDICATOR_SHORTNAME , "ТС 80-20");
  
  return(INIT_SUCCEEDED);
}

In der etatmässigen Funktion OnCalculate werden wir den Code des Hauptprogramms unterbringen — wir organisieren die Loop, die die Bars der laufenden Timeframe in Richtung aus der Vergangenheit in die Zukunft sortieren und sie auf das Vorhandensein des Signals mit Hilfe der Funktion aus dem Signalmodul prüfen. Vorläufig werden wir die Anfangswerte der notwendigen Variablen erklären und initialisieren. Die älteste Bar der Loop für die erste Berechnung werden wir unter Berücksichtigung der vom Benutzer aufgegebenen Beschränkung der Tiefe der History ( Bars_Limit ) bestimmen. Für die nachfolgenden Aufrufe werden wir nicht nur einer der letzten Bar rechnen, sondern alle Bars des laufenden Tages, doch gehört das Zweibar-Muster dem Chart D1, unabhängig von der laufenden Timeframe in Wirklichkeit.

Außerdem muss man die Maßnahmen für den Schutz vor den sogenannten Phantomen durchführen: wenn die Puffer des Indikators bei Neuinitialisierung zwangsläufig nicht gereinigt werden, so bleiben bei der Umschaltung der Timeframes oder dem Wechsel des Symbols am Bildschirm die gefärbten Bereiche, die im Chart nicht aktuell sind. Diese Aktion (die Reinigung der Puffer) muss man zum Aufruf der Funktion OnCalculatenach befestigen, der erste nach der Initialization des Indikators kommt. Aber bestimmen, dass es der erste Aufruf ist, kann man nur mit Hilfe einer standardmäßigen Variabel prev_calculated nicht — sie enthält Null nicht nur beim ersten Aufruf der Funktion, sondern auch "bei der Veränderung der Kontrollsumme". Wir werden eine bestimmte Zeit ausgeben, um dieses Problem mit dem Vorrat zu lösen — wir werden die Struktur unabhängige vom Rücksetzen der Variabel prev_calculated erstellen, die in den Indikatoren oft verwendeten und nützlichen Daten bearbeiten wird:

- die Flagge der ersten Funktion OnCalculate;

- bei der Veränderung nicht auf null setzbare Kontrollsumme der Zähler der berechneten Bars;

- die Flagge der Änderung der Kontrollsumme;

- Die Flagge des Anfanges der neuen Bar;

- Die Anfangszeit der laufenden Bars.

Dieser Daten vereinigende Struktur wird auf der globalen Ebene erklärt und kann sammeln oder die Informationen in/aus beliebiger etatmässiger oder Anwender- Funktionen anbieten. Solchem Programmwesen wird der Name 'der Hausgeist ' (Brownie) vollkommen passen. Sie können es am Ende des Indikator-Codes unterbringen. Dort werden wir eine globale Objekt-Struktur des Typs mit dem Namen go_Brownie erklären:

struct BROWNIE {                 // Der Hausgeist: die Struktur für die Speicherung und die Datenverarbeitung auf der globalen Ebene
  datetime  t_Last_Bar_Time;    // Die Zeit der letzten bearbeiteten Bar
   int        i_Prew_Calculated; // Die Zahl der berechneten Bars
  bool       b_First_Run;        // die Flagge des ersten Startsа
  bool       b_History_Updated;  // die Flagge der Erneuerung der HIstory
  bool      b_Is_New_Bar;        // Die Flagge der Eröffnung der neuen Bar
  
  BROWNIE() { // Konstruktor
    // Der standardmäßige Wert:
    t_Last_Bar_Time = 0;
    i_Prew_Calculated = WRONG_VALUE;
    b_First_Run = b_Is_New_Bar = true;
    b_History_Updated = false;
  }
  
  void f_Reset(bool b_Reset_First_Run = true) { // Das Rücksetzen der Variable
    // Der standardmäßige Wert:
    t_Last_Bar_Time = 0;
    i_Prew_Calculated = WRONG_VALUE;
    if(b_Reset_First_Run) b_First_Run = true; //Das Rücksetzen, wenn es die Erlaubnis gibt
    b_Is_New_Bar = true;
    b_History_Updated = false;
  }
  
   void f_Update(int i_New_Prew_Calculated = WRONG_VALUE) { // Die Erneuerung der Variabel
    // die Flagge des ersten Aufrufs der etatmässigen Funktion OnCalculate
    if(b_First_Run && i_Prew_Calculated > 0) b_First_Run = false;
    
    // Die neue Bar?
    datetime t_This_Bar_Time = TimeCurrent() - TimeCurrent() % PeriodSeconds();
    b_Is_New_Bar = t_Last_Bar_Time == t_This_Bar_Time;
    
    // Die Zeit-Erneuerung der laufenden Bars?
    if(b_Is_New_Bar) t_Last_Bar_Time = t_This_Bar_Time;
    
    if(i_New_Prew_Calculated > - 1) {
      // wenn es irgendwelche Änderungen in der History gibt?
      b_History_Updated = i_New_Prew_Calculated == 0 && i_Prew_Calculated > WRONG_VALUE;
      
      // prew_calculated Verwenden, wenn der 1-e Aufruf OnCalculate ist
      if(i_Prew_Calculated == WRONG_VALUE) i_Prew_Calculated = i_New_Prew_Calculated;
      // oder wenn es noch keine Erneuerung gab
      else if(i_New_Prew_Calculated > 0) i_Prew_Calculated = i_New_Prew_Calculated;
    }
  }
};
BROWNIE go_Brownie;

Wir werden die Information über 'den Hausgeist' und das Ereignis der Deinitialisierung des Indikators vorsehen:

void OnDeinit(const int reason) {
  go_Brownie.f_Reset(); // Den Hausgeist benachrichtigen
}

Falls es notwendig ist, können die Sammlung der Informationen, die im 'Hausgeist' gespeichert werden, noch mehr verbreitet werden, wenn die Anwenderfunktionen oder die Klassen zum Beispiel die Preisen, Volumen oder die Spread-Größe der laufenden Bar (Open, High, Low, Close, tick_volume, volume, spread) brauchen werden. Die fertigen Daten aus der Funktion OnCalculate zu nehmen und sie durch den 'Hausgeist' zu übergeben ist bequemer, als die Funktionen des Kopierens der Timeserie (CopyOpen, CopyHigh usw. zu verwenden. oder CopyRates) — dies wird die Ressourcen des Prozessors einsparen und wird von der Notwendigkeit befreien, um die Bearbeitung der Fehler dieser Funktionssprache zu organisieren.

Wir kehren zur Hauptfunktion des Indikators zurück. Die Erklärung der Variabel und die Vorbereitung der Massive unter Verwendung der Struktur go_Brownie wird so aussehen:

go_Brownie.f_Update(prev_calculated); // Information füe den Hausgeist

int
  i_Period_Bar = 0, // Der Hilfszähler
  i_Current_TF_Bar = rates_total - int(Bars_Limit) // Der Index der Bar, vom Anfang der Loop des laufenden TF
;
static datetime st_Last_D1_Bar = 0; // die Zeit der letzten verarbeiten Bars D1 (2-er Bar des Musters)
static int si_1st_Bar_of_Day = 0; // Der Index der ersten Bar des laufenden Tages

if(go_Brownie.b_First_Run) { // wenn es der erste Start ist
  // Die Puffer bei der Neuinitialisierung reinigen:
  ArrayInitialize(buff_1st_Bar_Inner, 0); ArrayInitialize(buff_1st_Bar_Inner_Zero, 0);
  ArrayInitialize(buff_1st_Bar_Outer, 0); ArrayInitialize(buff_1st_Bar_Outer_Zero, 0);
  ArrayInitialize(buff_Entry, 0); ArrayInitialize(buff_Entry_Color, 0 );
  ArrayInitialize(buff_Signal, 0); ArrayInitialize(buff_Signal_Color, 0);
  ArrayInitialize(buff_TP, 0);
  ArrayInitialize(buff_SL, 0);
   st_Last_D1_Bar = 0;
  si_1st_Bar_of_Day = 0;
} else { // это не 1й запуск
   datetime t_Time = TimeCurrent();
  // die minimale Tiefe der Neuberechnung - vom letzten Tag:
   i_Current_TF_Bar = rates_total - Bars(_Symbol, PERIOD_CURRENT, t_Time - t_Time % 86400, t_Time) - 1;
}
ENUM_ENTRY_SIGNAL e_Signal = ENTRY_UNKNOWN; // Signal
double
  d_SL = WRONG_VALUE, // die Ebene SL
  d_TP = WRONG_VALUE, // die Ebene TP
  d_Entry_Level = WRONG_VALUE , // die Ebene des Eintritts
  d_Range_High = WRONG_VALUE, d_Range_Low = WRONG_VALUE // Die Grenzen des Bereiches 1-e Bar des Musters
;
datetime
  t_Curr_D1_Bar = 0, // die Zeit der laufenden Bar D1 (2-e Bar des Musters)
  t_D1_Bar_To_Fill = 0 // die Zeit der Bar D1, die angemalt werden muss (1-e Bar des Musters)
;

// Zu kontrollieren, damit der Index der Anfangsbar der Umrechnung in den zulässigen Rahmen wird:
i_Current_TF_Bar = int(fmax(0 , fmin(i_Current_TF_Bar, rates_total - gi_Min_Bars)));

while(++i_Current_TF_Bar < rates_total && !IsStopped()) { // Die Sortierung der Bars des laufenden TF
  // hier wird die Hauptloop des Programms
}

Bei der Sortierung der Bars der laufenden Timeframe werden wir das Vorhandensein des Signals prüfen:

e_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_Signal > 1) continue; // am Tag, zu dem diese Bar gehört, gibt es kein Signal

Wenn das Signal gibt und es ist die erste Bar des neuen Tages, so muss man die Ausfüllung des Bereiches der vorhergehenden Tagesbar organisieren. Die Flagge wird der Wert der Variabel t_D1_Bar_To_Fill als datetime sein — wenn ihr der Wert WRONG_VALUE zugeordnet ist, so wird auf dieser Bar die Ausfüllung nicht gefordert. Auf dieser ersten Bar soll und die Signallinie anfangen, aber für die beste Wahrnehmung der Markierung werden wir sie bis zur letzten Bar des vorhergehenden Tages verlängern. Da die Berechnungen der Signalnebene und der Farbe der Linien und der gefärbten Bereiche für bullige und der bärische Bar unterschiedlich werden, werden wir zwei voneinander ähnlichen Blöcke machen:

t_Curr_D1_Bar = Time[i_Current_TF_Bar] — Time[i_Current_TF_Bar] % 86400; // Der Anfang des Tages, zu dem diese Bar gehört
if(st_Last_D1_Bar < t_Curr_D1_Bar) { //Es ist die Bar des neuen Tages
  t_D1_Bar_To_Fill = Time[i_Current_TF_Bar — 1] — Time [i_Current_TF_Bar — 1] % 86400;
  si_1st_Bar_of_Day = i_Current_TF_Bar;
}
else t_D1_Bar_To_Fill = WRONG_VALUE; // Die Bar des alten Tages, wird die neue Ausfüllung nicht gefordert
st_Last_D1_Bar = t_Curr_D1_Bar; // Speichern

if(t_D1_Bar_To_Fill != WRONG_VALUE) { // die neue Bar D1
  // Die Ausfüllung der Bar D1 vom vorgehenden Tag:
  i_Period_Bar = i_Current_TF_Bar;
  if(d_Entry_Level < d_Range_High) { // Die bärische Bar D1
     if(Show_Outer) while(--i_Period_Bar > 0) { // der vollständige Bereich
      if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
       buff_1st_Bar_Outer_Zero[i_Period_Bar] = d_Range_Low;
      buff_1st_Bar_Outer[i_Period_Bar] = d_Range_High;
    }
     if(Show_Inner) { // der innere Bereich
      i_Period_Bar = i_Current_TF_Bar;
      while (--i_Period_Bar > 0) {
        if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
        buff_1st_Bar_Inner_Zero[i_Period_Bar] = d_Range_Low + 0.2 * (d_Range_High — d_Range_Low);
        buff_1st_Bar_Inner[i_Period_Bar] = d_Range_High — 0.2 * (d_Range_High — d_Range_Low);
      }
    }
    // Der Anfang der Signal-Linie - von der letzten Bar des vorgehenden Tages
    buff_Signal[i_Current_TF_Bar] = buff_Signal[i_Current_TF_Bar — 1] = d_Range_Low — gd_Extremum_Break;
    buff_Signal_Color[i_Current_TF_Bar] = buff_Signal_Color[i_Current_TF_Bar — 1 ] = 0;
  } else { // Die bullische Bar D1
    if(Show_Outer) while(--i_Period_Bar > 0) { // der vollständige Bereich
      if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
      buff_1st_Bar_Outer_Zero[i_Period_Bar] = d_Range_High;
      buff_1st_Bar_Outer[i_Period_Bar] = d_Range_Low;
    }
    if(Show_Inner) { // der innere Bereich
      i_Period_Bar = i_Current_TF_Bar;
      while(--i_Period_Bar > 0) {
        if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
        buff_1st_Bar_Inner_Zero[i_Period_Bar] = d_Range_High — 0.2 * (d_Range_High — d_Range_Low);
         buff_1st_Bar_Inner[i_Period_Bar] = d_Range_Low + 0.2 * (d_Range_High — d_Range_Low);
      }
    }
    // Der Anfang der Signal-Linie - von der letzten Bar des vorgehenden Tages
    buff_Signal[i_Current_TF_Bar] = buff_Signal[i_Current_TF_Bar — 1] = d_Range_High + gd_Extremum_Break;
    buff_Signal_Color[i_Current_TF_Bar] = buff_Signal_Color[i_Current_TF_Bar — 1] = 1;
  }
} else continue;

Hier (innerhalb der Sortierungs-Loop der Bars der laufenden Timeframe) organisieren wir das Zeichen der übrigen Linien der Markierung. Ich werde erinnern, die Signallinie soll auf der Bar zu Ende gehen, wo ihr der Preis betroffen hat. Auf dieser Bar soll die Linie des Pending Orders anfangen. Sie soll auf der Bar des Kontaktes mit dem Preis zu Ende gehen, und auf dieser Bar sollen die Linien Take Profit und Stop Loss anfangen. Auf der Bar, wo der Preis einer von ihnen betrifft, wird die Markierung dieses Musters beendet:

// Die Signal-Linie bis dahin, wo ihre überquerende Bar ist:
i_Period_Bar = i_Current_TF_Bar;
if(d_Entry_Level < d_Range_High) { // Die bärische Bar D1
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
    buff_Signal[i_Period_Bar] = d_Range_Low — gd_Extremum_Break;
    buff_Signal_Color[i_Period_Bar] = 0;
    if(d_Range_Low — gd_Extremum_Break >= Low[i_Period_Bar]) break;
  }
} else { // Die bullische Bar D1
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
    buff_Signal[i_Period_Bar] = d_Range_High + gd_Extremum_Break;
    buff_Signal_Color[i_Period_Bar] = 1;
    if(d_Range_High + gd_Extremum_Break <= High[i_Period_Bar]) break;
  }
}

// Die Linie des Eintritts bis dahin, wo ihre überquerende Bar ist:
if(d_Entry_Level < d_Range_High) { // Die bärische Bar D1
   while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
    buff_Entry[i_Period_Bar] = d_Range_Low;
    buff_Entry_Color[i_Period_Bar] = 0;
     if(d_Range_Low <= High[i_Period_Bar]) {
      if(buff_Entry[i_Period_Bar — 1 ] == 0.) {
        // Der Anfang und das Ende auf einer Bar, verlängern wir es um 1 Bar in die Vergangenheit
        buff_Entry[i_Period_Bar — 1] = d_Range_Low;
        buff_Entry_Color[i_Period_Bar — 1] = 0;
      }
      break;
    }
  }
} else { // Die bullische Bar D1
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
    buff_Entry[i_Period_Bar] = d_Range_High;
    buff_Entry_Color[i_Period_Bar] = 1;
    if(d_Range_High >= Low [i_Period_Bar]) {
      if(buff_Entry[i_Period_Bar — 1] == 0.) {
        //Der Anfang und das Ende auf einer Bar, verlängern wir es um 1 Bar in die Vergangenheit
         buff_Entry[i_Period_Bar — 1] = d_Range_High;
        buff_Entry_Color[i_Period_Bar — 1] = 1;
      }
      break;
    }
  }
}

// Die Linien TP und SL bis zur Bar, die einer von ihnen gekreuzt hat:
if(d_Entry_Level < d_Range_High) { // Die bärische Bar D1
   // SL ist gleich dem Minimum vom Anfang des Tages:
  d_SL = Low[ArrayMinimum(Low, si_1st_Bar_of_Day, i_Period_Bar — si_1st_Bar_of_Day)];
  
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
    buff_SL[i_Period_Bar] = d_SL;
     buff_TP[i_Period_Bar] = d_TP;
    if(d_TP <= High[i_Period_Bar] || d_SL >= Low[i_Period_Bar]) {
      if(buff_SL[i_Period_Bar — 1] == 0.) {
        // Der Anfang und das Ende auf einer Bar, verlängern wir es um 1 Bar in die Vergangenheit
        buff_SL[i_Period_Bar — 1] = d_SL;
        buff_TP[i_Period_Bar — 1] = d_TP;
      }
      break ;
    }
  }
} else { // Die bullische Bar D1
  // SL ist gleich dem Maximum vom Anfang des Tages:
  d_SL = High[ArrayMaximum(High, si_1st_Bar_of_Day, i_Period_Bar — si_1st_Bar_of_Day)];
  
  while(++i_Period_Bar < rates_total) {
    if(Time [i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
    buff_SL[i_Period_Bar] = d_SL;
    buff_TP[i_Period_Bar] = d_TP;
    if(d_SL <= High[i_Period_Bar] || d_TP >= Low[i_Period_Bar]) {
      if(buff_SL[i_Period_Bar — 1] == 0.) {
        //Der Anfang und das Ende auf einer Bar, verlängern wir es um 1 Bar in die Vergangenheit
        buff_SL[i_Period_Bar — 1] = d_SL;
        buff_TP[i_Period_Bar — 1] = d_TP;
      }
      break ;
    }
  }
}

Außer der Loop werden wir den Aufrufs-Code der Benachrichtigungsfunktion über das Signal f_Do_Alert platzieren. In Wirklichkeit, ihre Möglichkeiten ist in diesem Indikator ein wenig breiter eingesetzt — die Funktion kann mit den Sounds-Dateien arbeiten, d.h., man kann den Einschluss dieser Option und die Auswahl der geteilten Dateien für die Signale für den Kauf und den Verkauf hinzufügen. Die Listing der Funktion:

void f_Do_Alert(                   // Die Funktion des Versandes der Signale und der Benachrichtigungen
   string  s_Message,              // Der Text für die Warnung
  bool    b_Alert = true,         // Sollen die auftauchenden Fenster gezeigt werden?
  bool    b_Sound = false,        // Soll die Sounds-Datei abgespielt werden?
  bool    b_Email = false,        // Soll eMail geschickt werden?
  bool    b_Notification = false, // Soll push-Benachrichtigung geschickt werden?
  string  s_Email_Subject = "",   // Das Thema der eMail-Nachricht
  string  s_Sound = "alert.wav"   // Die Sounds-Datei
) {
  static string ss_Prev_Message = "Es gab die Ruhe"; // Der Text der vorherigen Warnung
  static datetime st_Prev_Time; // Die Barszeit der vorherigen Warnung
  datetime t_This_Bar_Time = TimeCurrent () — PeriodSeconds() % PeriodSeconds(); // Die Zeit der vorherigen Bar
  
  if (ss_Prev_Message != s_Message || st_Prev_Time != t_This_Bar_Time) {
    // Die Warnung der anderen oder/und 1-en auf dieser Bar
    
    // Speichern:
    ss_Prev_Message = s_Message;
    st_Prev_Time = t_This_Bar_Time;
    
    // Die Bildung des Satzes für eine Nachricht:
    s_Message = StringFormat("%s | %s | %s | %s" ,
      TimeToString(TimeLocal(), TIME_SECONDS), // Die lokale Zeit
      _Symbol, // Symbol
      StringSubstr(EnumToString(ENUM_TIMEFRAMES(_Period)), 7), // ТФ
      s_Message // Nachricht
    );
    
    // Das Meldung-Signal geben:
    if(b_Alert) Alert(s_Message);
    if(b_Email) SendMail(s_Email_Subject + " " + _Symbol, s_Message);
    if(b_Notification) SendNotification(s_Message);
    if(b_Sound) PlaySound(s_Sound);
  }
}

Der Prüfungs-Code der Aufrufs-Notwendigkeit dieser Funktion und die Bildung des Textes für sie, der im Körper des Programms aufgestellt ist, bevor der Bearbeiter des Ereignisses OnCalculate aus wird:

// алерт
i_Period_Bar = rates_total — 1; // die laufende Bar

if(Alert_Popup + Alert_Email + Alert_Push == 0) return(rates_total); // alles ist schon ausgemacht
if(buff_Signal[i_Period_Bar] == 0) return(rates_total); // oder es gibt noch nichts zu holen
if(
  buff_Signal[i_Period_Bar] > High[i_Period_Bar]
  ||
  buff_Signal[i_Period_Bar] < Low[i_Period_Bar]
) return(rates_total); // Es gibt keine Berührung der Signal-Linie

// Der Text der Meldung:
string s_Message = StringFormat( "TS 80-20: нужен %s @ %s, TP: %s, SL: %s",
  buff_Signal_Color[i_Period_Bar] > 0 ? "BuyStop" : "SellStop",
  DoubleToString(d_Entry_Level, _Digits),
  DoubleToString(d_TP, _Digits),
  DoubleToString(d_SL, _Digits)
);
// Die Benachrichtigung:
f_Do_Alert(s_Message, Alert_Popup, false, Alert_Email, Alert_Push, Alert_Email_Subj);

return(rates_total); // Das Betriebsende OnCalculate

Der ganze Anfangsode des Indikators im Ganzen gibt es in den befestigten Dateien, sein Name ist — TS_80-20.mq5. Betreffs seiner Verwendung — ist am besten die Markierung des Handels nach diesem System auf den minutenlangen Charts sichtbar.

Es gibt eine wesentliche Anmerkung zu dieser Markierung — der Indikator verwendet die Daten der Bars, und nicht die Reihenfolgen der Ticks innerhalb der Bars. D.h. wenn auf einer Bar der Preis einige Linien der Markierung (zum Beispiel, die Linie Take Profit und Stop Loss) kreuzte, kann man nicht immer bestimmen, welche von ihnen als gekreuzt wurde. Der andere Fehler ist damit verbunden, dass die Bars des Anfanges und des Schlusses der Linie nicht übereinstimmen können, sonst werden die Linien aus dem Puffer wie DRAW_LINE und DRAW_COLOR_LINE einfach dem Benutzer nicht sichtbar sein. Diese Besonderheiten machen die Markierung nicht hundertprozentig genau, aber doch sehr anschaulich.


Der Expert Advisor für den Test der Trading-Strategie (TS) '80-20'

Der grundlegende EA für die Prüfung der Strategien aus dem Buch Street Smarts: High Probability Short-Term Trading Strategies wurde grundlich im ersten Artikel beschrieben. Wir werden zu ihm zwei wesentliche Änderungen eingeben. Die Erste ist damit verbunden, dass das Signalmodul auch im Indikator verwendet wird, das heißt, es wird rational, ihn in die Berechnung der Handelsebene zu tragen. Das haben wir schon oben gemacht — die Funktion fe_Get_Entry_Signal, außer dem Status des Signals, setzt die Setzung-Ebene des Orders Stop Loss und Take Profit zurück. Deshalb werden wir aus der vorhergehenden Version des EAs den entsprechenden Teil des Codes entfernen, wir werden Variable für die Aufnahme der Ebene aus der Funktion hinzufügen und wir werden den Aufruf dieser Funktion editieren. Ich werde hier die Listing der alten und neuen Blöcke des Codes nicht aufführen, Sie können sie in der verwandten Datei (die Zeile von 128 bis 141) anschauen.

Die zweite wesentliche Ergänzung zum Code des grundlegenden EAs ist damit verbunden, dass diese TS mit der kurzfristigen Tendenz zu tun hat, im Unterschied zu den letzten zwei. Sie vermutet, dass der Rücklauf einmal im Laufe der Tage stattfinden wird und es ist kaum möglich, dass es sich wiederholt. Das bedeutet, der Roboter soll nur einen Eintritt machen, und während der ganzen bleibenden Zeit bis zum nächsten Tag das existierende Signal ignorieren. Realisieren ist es am meisten einfacher mit Hilfe der speziellen Flagge — der statischen oder globalen Variable wie bool im Speicher des Programms. Aber wenn die Arbeit des Experten aus irgendwelchem Grund unterbrochen sein wird (es wird das Terminal geschlossen, der Experte wird vom Chart entfernt usw.), so wird auch die Bedeutung der Flagge verloren. Das bedeutet, auch nach dem Restart soll der Experte eine Möglichkeit haben, zu überprüfen, ob das heutige Signal früher durchgearbeitet wurde oder nicht. Dazu kann man die History der Trades für heute analysieren, und man kann das Datum des letzten Eintritts in den globalen Variabel des Terminals gespeichert haben, und nicht in die Programme. Wir werden die zweite Variante verwenden — er ist wesentlich einfacher in der Realisierung.

Wir werden dem Benutzer ermöglichen, die Option 'ein Eintritt im Tag' zu steuern, sowie, den Identifikator jeder gestarteten Version des Roboters einzustellen — er ist für die Verwendung der globalen Variable der Terminalebene nötig:

input bool  One_Trade = false;    // Eine Position im Tag
input uint  Magic_Number = 2016;  // Der Identifikator des EAs (Magic Number)

Im Bestimmung-Block der globalen Variable der Programme werden wir die Erklärung der nötigen Variablen für die Realisierung der Option 'ein Eintritt im Tag' hinzufügen. In der Funktion OnInit analysieren wir sie:

string
  gs_Prefix // Der Identifikator der Namen der (Super) globalen Variablen
;
bool
  gb_Position_Today = false,
  gb_Pending_Today = false
;

int OnInit() {

...

  // Die Erstellung der Präfix-Namen der (Super) globalen Variablen:
  gs_Prefix = StringFormat("SSB %s %u %s", _Symbol, Magic_Number, MQLInfoInteger(MQL_TESTER) ? "t " : "");
  
  // Hat der Roboter heute mit den Markts oder Pending-Orders gearbeitet?
  gb_Position_Today = int(GlobalVariableGet(gs_Prefix + "Last_Position_Date")) == TimeCurrent() — TimeCurrent() % 86400;
  gb_Pending_Today = int(GlobalVariableGet(gs_Prefix + "Last_Pending_Date")) == TimeCurrent() — TimeCurrent() % 86400;

...
}

Hier liest der Roboter die Werte der globalen Variable und vergleicht die in ihnen aufgezeichnete Zeit mit der Zeit des Tagesanfangs — so bestimmt er, ob das heutige Signal schon durchgearbeitet wurde. Die Aufzeichnung der Zeit in diesen Variablen organisieren wir an zwei Stellen — zum Code der Anlage des Pending Orders werden wir den entsprechenden Block hinzufügen (schon ausgewählt):

if(i_Try != -10) { //Es ist nicht gelungen, den Pending Order zu setzen
  if(Log_Level > LOG_LEVEL_NONE) Print("Fehler bei der Setzung des Pending-Orders");
  // Der Abstand vom laufenden Preis ist nicht ausreichend:(
  if(Log_Level > LOG_LEVEL_ERR)
    PrintFormat("Man kann nicht den Pending Order auf die Ebene %s setzen. Bid: %s Ask: %s StopLevel: %s",
      DoubleToString(d_Entry_Level, _Digits),
      DoubleToString(go_Tick.bid, _Digits),
      DoubleToString(go_Tick.ask, _Digits),
      DoubleToString(gd_Stop_Level, _Digits)
    );
} else { // удалось
  // Aktualisierung der Flagge:
  GlobalVariableSet( // in den globalen Variablen des Terminals
    gs_Prefix + "Last_Pending_Date",
    TimeCurrent() — TimeCurrent() % 86400
  );
  gb_Pending_Today = true; //in den globalen Variablen des Programms
}

  Den zweiten Block setzen wir nach dem Code, der eine übergeöffnete Position bestimmt:

if(PositionSelect(_Symbol)) { // es gibt eine geöffnete Position
        if(PositionGetDouble(POSITION_SL) == 0.) { // die neue Position
                
                if(!gb_Position_Today) { // das ist die 1-e Position heute
                        // Aktualisierung der Flagge:
                        GlobalVariableSet( // in den globalen Variablen des Terminals
                                gs_Prefix + "Last_Position_Date",
                                TimeCurrent() — TimeCurrent() % 86400
                        );
                        gb_Position_Today = true; // in den globalen Variablen des Programms
                }
...

Es gibt keinen anderen wesentlichen Veränderungen im Code der vorhergehenden Version des EAs Der Anfangscode der neuen Version in der endgültigen Art gibt es im Anhang zum Artikel.

 

Der Test der Strategie auf historischen Daten

Die Autoren des Handelssystems als Bestätigung ihrer Lebensfähigkeit führen die Muster auf den Charts des Endes des vorherigen Jahrhunderts auf, und wir müssen ihre Aktualität unter den Bedingungen des gegenwärtigen Marktes prüfen. Für den Test habe ich das populärste Paar EURUSD aus dem Markt-Forex genommen, sowie volatilitätes USDJPY und einer der Edel-Metalle — XAUUS. Von Raschke und Connorson hingewiesenen Einzüge habe ich um 10 Male vergrössert, da in diesen Zeiten vierzählige Notierungen verwendet wurden, und ich prüfte den EA auf den Fünfzählige. Ohne irgendwelche Autor-Hinweise bezüglich der Parameter des Trawls habe ich diejenigen gewählt, die am meisten passend für eine Tages-Timeframe erschienen und das Instrument der Volatilität. Das gleiche betrifft auch die originellen Regeln des Rechnungs-Algorithmus Take Profit — der Koeffizient für seine Rechnung wurde beliebig, ohne tiefe Optimierung gewählt.

Das Chart der Veränderung der Bilanz bei der Prüfung in der fünfjährigen History EURUSD mit den originellen Regeln (ohne Take Profit):

EURUSD D1 5 Jahre

Mit den gleichen Einstellungen und der Ergänzung Take Profit:

EURUSD D1 5 Jahre

Das Chart der Veränderung der Bilanz bei der Prüfung der originellen Regeln in der fünfjährigen History USDJPY:

USDJPY D1 5 Jahre

Dasselbe Instrument und die Timeframe mit den selben Einstellungen, aber mit der Ergänzung Take Profit:

USDJPY D1 5 Jahre

Die originellen Regeln auf den Tagesnotierungen des Goldes in letzten 4 Jahren zeigen ein solches Chart der Veränderung der Bilanz:

XAUUSD D1 4 Jahre

Die volle Informationen über die in jeder Prüfung verwendeten Einstellungen des Roboters kann man im Artikel hinzugefügten Archiv erfahren — darin gibt es die vollen Berichte von jeder Prüfung. 


Fazit

Die im Signalmodul programmierten Regeln entsprechen der Beschreibung des Handelssystems 80-20's aus dem Buch Lindy Raschke und Lawrance Konnors Street Smarts: High Probability Short-Term Trading Strategies. Es gibt auch eine kleinere Erweiterung der Autors-Regeln. Diese Instrumente müssen (der Roboter und der Indikator,) den Benutzern helfen, die selbständig Schlüsse über die Aktualität der TS unter den Bedingungen des gegenwärtigen Marktes ziehen wollen. Nach meiner bescheidenen Meinung braucht sie eine ernste Modernisierung. Im Artikel habe ich mich bemüht, detailliert die Bildung des Codes des Signalmoduls und seines verwendenden Roboters und des Indikators zu kommentieren — ich hoffe, es wird helfen, wer sich für eine solche Modernisierung entscheidet. Außer der Upgrade der Regeln, kann man versuchen, die zum System besser passenden Handelsinstrumente auszusuchen, die Parameter der Aussuche des Signals und der Begleitung der Positionen. 


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

Beigefügte Dateien |
Reports.zip (597.76 KB)
MQL5.zip (124.99 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (2)
Lars Rompe
Lars Rompe | 13 Dez. 2016 in 11:13

Well prepared and structured article - thank you for taking the effort.

Also the code is commented (even if I don't speak Russian very well)

Thanks for sharing!

demleitner
demleitner | 28 März 2020 in 20:42

Russian? No problem! I speak German and reasonable English and found my way:

Put Windows on Russian for 8-bit characters. My Windows 10 is in German now, Later I can add, how to do it in English Windows 10. Now for German Windows 10 - this is the way:

Open "Systemsteuerung"

switch to "Große Symbole" or "Kleine Symbole"

From there open "Region"

On the top you find 2 pages, named "Formate" and Verwaltung" switch to "Verwaltung"

Now you see two buttons (where you need Administrator-rights) "Einstellungen kopieren..." and "Gebietsschema ändern"

The latter one is what you need, "Gebietsschema ändern". Here you choose "Russisch (Russische Föderation"

You need to restart, by clicking OK and your way out of there the machine will tell you.

Now ASCII 7-bit is still correct (English) but the higher letters (above 127) are now Russian.



Then you can copy the Russian from Metaeditor and paste it to a translator i.e. translate.google.com.

I recommend to translate to English - most of the time it looks like any US-programmer did it. to German is very often terrible (just my opinion).

Greetings to the non-Russian-speaking world (and to the Russian spoken authors as well,

Gerhard

Die statistische Verteilung in Form von einem Histogramm ohne Indikator-Puffer und Arrays Die statistische Verteilung in Form von einem Histogramm ohne Indikator-Puffer und Arrays
Im Artikel wird die Bildungsmöglichkeit der Histogramme der statistischen Verteilungen der Markt-Charakteristiken unter Verwendung des graphischen Gedächtnisses betrachtet, das heißt ohne Verwendung der Indikator-Puffer und Arrays. Es wurden die ausführlichen Beispiele des Aufbaus solcher Histogramme aufgeführt und wurde die sogenannte "verborgene" Funktional der graphischen Objekte der Sprache MQL5 vorgeführt.
Universeller ZigZag Universeller ZigZag
ZigZag ist einer der beliebtesten Indikatoren unter MetaTrader 5 Nutzern. Im Artikel werden die Möglichkeiten der Erstellung verschiedener Varianten des ZigZag Indikators analysiert. Als Ergebnis bekommen wir einen universellen Indikator mit breiten Möglichkeiten für die Erweiterung der Funktionalität, den man bei der Entwicklung neuer Expert Advisors und anderer Indikatoren verwenden kann.
LifeHack für Trader: Der vergleichende Bericht über einige Tests LifeHack für Trader: Der vergleichende Bericht über einige Tests
Im Artikel wird der Test des EAs betrachtet, der zugleich auf 4 verschiedenen Symbolen gestartet wird. Der endgültige Vergleich der 4 Testberichte wird in einer Tabelle aufgeführt, genauso wie bei einer Auswahl der Waren in einem Internet-Geschäft. Als zusätzlicher Bonus kommen dazu die automatisch erstellten Grafiken der Verteilung für jedes Symbol.
Das Handelssystem 'Turtle Soup' und seine Modifikation 'Turtle Soup Plus One' Das Handelssystem 'Turtle Soup' und seine Modifikation 'Turtle Soup Plus One'
In diesem Artikel wurden Regeln der Handelsstrategien Turtle Soup und Turtle Soup Plus One aus dem Buch Street Smarts: High Probability Short-Term Trading Strategies von Linda Raschke und Laurence Connors formuliert und programmiert. Die im Buch beschriebenen Strategien sind relativ populär, man sollte aber beachten, dass die Autoren diese Strategien anhand eines 15...20 Jahre alten Marktverhaltens entwickelt haben.