Der universell Oszillator mit dem graphischen Interface

Dmitry Fedoseev | 10 Januar, 2017


Inhalt

Einführung

Die Auswahl des passenden Indikators für das Handelssystem wird nach den sorgfältigen vorläufigen Beobachtungen der verschiedenen Indikatoren auf dem Chart durchgeführt — ihrer Sortierungen und Experimente mit den Parametern. Wenn der Indikator jedes Mal aus dem Fenster des Navigators gezogen wird, und für jeden Parameter-Wechsel das Fenster der Eigenschaften geöffnet wird, kann dieser Prozess lange dauern. Es wäre nicht schlecht, diesen Prozess zu beschleunigen.

Durch die Erstellung des graphischen Interface, das unmittelbar auf dem Chart erreichbar sein wird, kann man sehr schnell die Parameter des Indikators wechseln, und gleich vor Ort den Effekt ihrer Veränderung beobachten. Die Vereinigung der verschiedenen Indikatoren in einem allgemeinen Indikator mit dem graphischen Interface ermöglicht ebenso die Indikatoren schnell zu wechseln.

Die Analyse der Aufgabe

Selbst die Aufgabe der Erstellung des universellen Oszillators - ist nicht besonders schwierig. Es wird ein wenig ein Objekt-orientiertes Programmieren erforderlich: die grundlegende Klasse und eine Menge der derartigen Tochterklassen.

Die Parameter jedes konkreten Indikators werden in den Konstruktor der Tochterklasse übergeben. In diesem Fall wird bei der Erstellung des Objektes der Editor MetaEditor den Tipp mit der Liste der Parameter öffnen, welcher den Prozess der Entwicklung wesentlich erleichtern wird (Abb. 1).

 
in Abb. 1. Der Tipp mit den Parametern des Konstrukteurs bei der Erstellung des Objektes

Die Hauptkomplexität könnte bei der praktischen Verwendung eines solchen Indikators entstehen. Es handelt sich darum, dass die Sätze der äußerlichen Parameter bei verschiedenen Oszillatoren stark unterschiedlich sind. Wenn es für jeden Oszillator seine eigenen Parameter machen, die sich mit dem Präfix unterscheiden, so wird der Indikator nicht manuell benutzbar, aber für die Verwendung mittels der Funktion iCustom() oder IndicatorCreate() kann er untauglich wegen einer großen Menge der Parameter sein. In die Funktion IndicatorCreate() kann man nicht mehr als 256 Parameter übergeben, und in die Funktion iCustom() — nicht mehr als 64. In dieser Anzahl werden auch die allgemeinen Parameter der Typ eines Symbols oder Namen der Indikator berücksichtigt, so dass die tatsächliche Anzahl der erreichbaren Parameter ein wenig weniger ist. Es ist möglich, einen kleinen universellen Satz der Parameter zu verwenden, aber dann wird der Indikator unbequem bei der Benutzung: man muss dann immer die Informationsanleitung anschauen, um zu erfahren, welche Parameter für diesen oder jenen Indikator eingesetzt sind.

Die Verwendung des graphischen Interfaces löst das vorliegende Problem in bedeutendem Ausmaß: in seinem Fenster kann man die Steuerungselemente darstellen, die nur dem konkreten gewählten Indikator entsprechen. Die Möglichkeit des Aufrufs des gegebenen Indikators mit der Funktion iCustom() oder IndicatorCreate() soll auch gewährleistet werden, deshalb werden im Fenster der Eigenschaften des Indikators auch die äußerlichen Parameter angezeigt, aber, wie es höher geschrieben ist, es wird ein kleiner universeller Satz.

Der Satz der Parameter

Wir bestimmen den geforderten minimalen Satz der äußerlichen Parameter. Schauen wir mal die Liste der Oszillatoren im Terminal: das Hauptmenü - Einfügen - die Indikatoren - Oszillatoren, tragen wir sie in die Tabelle ein.

Tabelle 1. Alle Oszillatoren des Terminals

Funktion Titel Puffer Parameter
iATR Average True Range 1. Linie 1. int ma_period — die Periode der Mittlung
iBearsPower Bears Power 1. Histogramm 1. int ma_period — die Periode der Mittlung
iBullsPower Bulls Power 1. Linie 1. int ma_period — die Periode der Mittlung
iCCI Commodity Channel Index 1. Linie 1. int ma_period — die Periode der Mittlung
2. ENUM_APPLIED_PRICE applied_price — Der Typ des Preises
iChaikin Chaikin Oscillator 1. Linie 1. int fast_ma_period — die schnelle Periode  
2. int slow_ma_period — die langsame Periode
3. ENUM_MA_METHOD ma_method — der Typ der Glättung
4. ENUM_APPLIED_VOLUME applied_volume — der verwendete Volumen 
iDeMarker DeMarker 1. Linie 1. int ma_period — die Periode der Mittlung
iForce Force Index 1. Linie 1. int ma_period — die Periode der Mittlung  
2. ENUM_MA_METHOD ma_method — der Typ der Glättung  
3. ENUM_APPLIED_VOLUME applied_volume — der Typ der Volumen für die Berechnung 
iMomentum Momentum 1. Linie 1. int mom_period — die Periode der Mittlung
2. ENUM_APPLIED_PRICE applied_price — Der Typ des Preises
iMACD Moving Averages Convergence-Divergence 1. Histogramm
2. Linie
1. int fast_ema_period — die Periode des schnellen Mittelwerts
2. int slow_ema_period — die Periode des langsamen Mittelwerts
3. int signal_period — die Periode der Mittlung der Verschiedenheit
4. ENUM_APPLIED_PRICE  applied_price — Der Typ des Preises
iOsMA Moving Average of Oscillator (MACD histogram) 1. Histogramm 1. int fast_ema_period — die Periode des schnellen Mittelwerts 
2. int slow_ema_period — die Periode des langsamen Mittelwerts
3. int signal_period — die Periode der Mittlung der Verschiedenheit 
4. ENUM_APPLIED_PRICE  applied_price — Der Typ des Preises
iRSI Relative Strength Index 1. Linie 1. int ma_period — die Periode der Mittlung 
2. ENUM_APPLIED_PRICE applied_price — Der Typ des Preises
iRVI Relative Vigor Index 1. Linie
2. Linie
1. int ma_period — die Periode der Mittlung
iStochastic Stochastic Oscillator 1. Linie
2. Linie
1. int Kperiod — K- die Periode (die Anzahl der Bars für die Berechnung)
2. int Dperiod — D-die Periode (die Periode der ersten Glättung)
3. int slowing — die endgültige Glättung
4. ENUM_MA_METHOD ma_method — der Typ der Glättung
5. ENUM_STO_PRICE price_field — die Berechnungsart des Stochastiks 
iTriX Triple Exponential Moving Averages Oscillator 1. Linie 1. int ma_period — die Periode der Mittlung 
2. ENUM_APPLIED_PRICE applied_price — Der Typ des Preises
iWPR Williams' Percent Range 1. Linie 1. int calc_period — die Periode der Mittlung

In der Kolonne "Parameter" machen wir eine Liste aller Parameter-Arten und bestimmen ihre maximale Anzahl.

Tabelle 2. Die Typs und die Anzahl der Parameter 

Typ Anzahl
int 3
ENUM_APPLIED_PRICE 1
ENUM_MA_METHOD 1
ENUM_APPLIED_VOLUME 1
ENUM_STO_PRICE 1
In der Spalte "Puffer" sieht man, dass es ins Gesamt zwei Indikator-Puffer verwendet werden können und dass für verschiedene Indikatoren verschiedener Typ des Zeichnens gefordert wird. Natürlich, können sie mit Linien gezeichnet werden, aber von daher, dass manche von ihnen üblicherweise in Form eines Histogrammen dargestellt werden sollen, und es gibt Möglichkeiten des Terminales, dies zu machen, so werden wir uns beim Wechsel des Indikatorstyps bemühen, auch den Wechsel des Zeichentyps zu gewährleisten. Auch muss man über das Zeichnen der horizontalen Ebene nachdenken, da für einige Indikatoren (RSI, CCI u.ä.) ihr Vorhandensein auf dem Chart gewünscht ist.

Der Arbeitsplan

Je möglicher ist, eine allgemeine Aufgabe auf die größere Anzahl von den unabhängigen Aufgaben zu teilen, desto einfacher und bequemer ist sie, zu erfüllen. Deshalb wird die ganze Arbeit aus drei Etappen bestehen:

  1. Die Erstellung der Klassen für einen universellen Oszillator und die Erstellung dieses Oszillators ohne graphisches Interface.
  2. Die Erstellung der Klassen für das graphische Interface.
  3. Die Vereinigung des universellen Oszillators und des graphischen Interfaces. 

Ein wichtiger Moment, auf den man beachten muss, sind sie Werte der standardmäßigen Parameter. Die Parameter sollen sowie durch das graphische Interface installiert werden können, als auch es durch das Fenster der Eigenschaften (für die Versorgung der maximalen vielseitigen Anwendbarkeit des erstellten Indikators). Bei der Verwendung der Parameter-Installierung durch das Fenster der Eigenschaften, welches den kleinen universellen Satz der Parameter hat, muss man vorsehen, damit alle Indikatoren im Allgemeinen der natürlichen Art mit den standardmäßigen Parametern entsprechen.

Wir betrachten die standardmäßigen Werte bei verschiedenen Oszillatoren. Zum Beispiel, bei Stochastic sind die Perioden 5, 3, 3 (der erste Parameter ist größer es der zweite), und bei MACD — 12, 26, 9 (der erste Parameter ist weniger als der zweite). Bei dem Indikator MACD ist der erste Parameter — ist die schnelle Periode des Mittelwerts, und der zweiter ist die langsame Periode des Mittelwerts; das bedeutet, der erste Parameter soll weniger als der zweite sein. Ein solches Verhältnis der ersten und zweiten Parameter wird auch zum Oszillator Tschajkina passen (bei ihm werden auch die Perioden der schnellen und langsamen Mittelwerts eingegeben). Für den Indikator Stochastic ist das gegebene Verhältnis nicht so wichtig, er wird auf jeden Fall entsprechend der Bewegung des Preises aussehen. Wenn dem Indikator MACD der erste Parameter größer als der zweite eingestellt wird, dann wird der Indikator in die Richtung gerichtet, die der Bewegung des Preises entgegengesetzt ist (bei der standardmäßigen Installation des Parameters ist es gewünscht, dies zu vermeiden).

Unter Verwendung des graphischen Interfaces wäre es besser, damit die Indikatoren die Arbeit erstens mit den gewohnheitsmäßigen Sätzen der standardmäßigen Parameter beginnen: MACD mit den Perioden 12, 26, 9, Stochastic mit den Perioden 5, 3, 3, usw. Außerdem sollte man die Möglichkeit haben, damit er bei der Auswahl des neuen Indikators die Arbeit entweder mit den standardmäßigen Parametern oder mit den Parametern beginne, mit denen der vorhergehende Indikator arbeitete. Zum Beispiel, es werden die Indikatoren RSI und CCI geforscht, und uns ist es interessant, anzuschauen, wie sich die Art der Linie bei verschiedenen Indikatoren ändern, aber bei den identischen Werten der Periode. Es ist nötig, im Folgenden bei der Entwicklung der Klassen zu meinen.

Die Erstellung der grundlegenden Klasse des Indikators

Im Ordner Include erstellen wir den Ordner "UniOsc", in ihm werden alle zusätzlichen Dateien des Indikators zugeordnet. Der Satz der verwendet Oszillatoren ist bestimmt höher in der Tabelle 1. Entsprechend ihr werden wir die Aufzählung für die Auswahl des Oszillatorstyps erstellen. Möglicherweise wird die Aufzählung nicht nur in der Datei des Indikators gefordert, deshalb werden wir es in der separaten Datei unter dem Namen "UniOscDefines.mqh" zuordnen (die Datei befindet sich im Ordner "UniOsc"): 

enum EOscUnyType{
   OscUni_ATR,
   OscUni_BearsPower,
   OscUni_BullsPower,  
   OscUni_CCI,
   OscUni_Chaikin,
   OscUni_DeMarker,
   OscUni_Force,
   OscUni_Momentum,
   OscUni_MACD,
   OscUni_OsMA,
   OscUni_RSI,
   OscUni_RVI,
   OscUni_Stochastic,
   OscUni_TriX,
   OscUni_WPR
};

Es wird in dieser Datei nichts mehr.

Wir erstellen die Datei "CUniOsc.mqh" für die Klasse des Indikators, wir werden in ihm die Schablone der Klasse COscUni aufzeichnen:

class COscUni{
   protected:
    
   public:

};

In der Schablone ist die Sektion protected bestimmt, da einige Mitglieder der Klasse geschützt sind, aber sie sind für die Nachfolge der Klasse erreichbar(die Mitglieder der Sektion private sind geschützt, aber sie sind für die Nachfolge nicht erreichbar).

Die Hauptmethode der grundlegenden Klasse — die Methode, die der Funktion OnCalculate() des Indikators entspricht, wir nennen es Calculate(). Die zwei ersten Parameter der Methode werden den ähnlichen Parametern der Funktion OnCalculate() entsprechen: rates_total (die Gesamtmenge der Bars) und prew_calculate (die Anzahl der berechneten Bars). Keine Arrays müssen mit den Daten in die Methode Calculate() übergeben werden, da die Daten anderer Indikators verwendet werden. Aber man muss zwei Indikator-Puffer übergeben, die die Daten ausfüllen werden. Man muss sogar unter Verwendung der Indikatoren mit einem Puffer auf die Sauberkeit des zweiten Puffers folgen, deshalb werden auf jeden Fall zwei Indikator-Puffer in die Methode Calculate() übergeben. Der Code der Methode Calculate() wird vom Typ des verwendet Oszillators abhängig — ob er einen Puffer mit den Daten oder zwei hat. Das bedeutet, die Methode Calculate() wird virtuell sein:

virtual int Calculate( const int rates_total,
               const int prev_calculated,
               double & buffer0[],
               double & buffer1[]
){
   return(rates_total);
}

Bei der Ladung der verschiedenen Indikatoren wird eine Variable für "Handel" des Indikators nötig. Erklären wie sie in der Sektion protected.

Außerdem werden noch einige Variable gefordert — für verschiedene Eigenschaften der Darstellung der Puffers. Diese Eigenschaften werden sich bei der Ladung jedes konkreten Indikators bestimmt, das heißt, die Werte für diese Variable werden in den Tochterklassen verliehen:

int m_handle;           // Der Handle des Indikators
int m_bufferscnt;       // Die Anzahl der verwendeten Puffers    
string m_name;          // Der Name des Indikators      
string m_label1;        // Der Name des Puffers 1    
string m_label2;        // Der Name des Puffers 2
int m_drawtype1;        // Der Typ des Zeichens des Puffers 1    
int m_drawtype2;        // Der Typ des Zeichens des Puffers 2      
string m_help;          // eine kleiner Hinweis über die Parameter des Indikators
int m_digits;           // die Anzahl der Zeichen nach dem Komma beim Wert des Indikators
int m_levels_total;     // Die Anzahl der Ebene
double m_level_value[]; // Das Array der Ebene-Werte

Nach der Ladung des Indikators muss man seine Lage prüfen, das bedeutet, es wird die entsprechende Methode für die Überprüfung des Handels des Indikators gefordert:

bool CheckHandle(){
   return(m_handle!=INVALID_HANDLE);
}

Beim Wechsel des Oszillators durch das graphische Interface ist es erforderlich, zu bestimmen, ob der Indikator vollständig berechnet wurde. Das wird von der Funktion BarsCalculated() erledigt, deren Aufruf den Handel des Indikators erfordert. Das bedeutet, wir werden die Methode für die Erhaltung des Handels hinzufügen:  

int Handle(){
    return(m_handle);
}

Im Konstrukteur der Klasse initialisieren wir den Handel, und in der Destruktion überprüfen wir ihn und falls es notwendig ist, rufen wir die Funktion IndicatorRelease() auf:

void COscUni(){
   m_handle=INVALID_HANDLE;
}

void ~COscUni(){
   if(m_handle!=INVALID_HANDLE){
      IndicatorRelease(m_handle);
   }
} 

Wir werden den Zugang zu übrigen Variablen besorgen, die die Darstellung verschiedener Indikatoren bestimmen, erstellen die Methoden für die Erhaltung ihrer Werte:

string Name(){ // Der Name des Oszillators
   return(m_name);
}    
  
int BuffersCount(){ // Die Anzahl der Oszillator-Puffer
   return(m_bufferscnt);
}

string Label1(){ // Der Name des ersten Puffers
   return(m_label1);
}

string Label2(){ // Der Name des zweiten Puffers
   return(m_label2);
}

int DrawType1(){ // Der Typ des Zeichens des ersten Puffers
   return(m_drawtype1);
}

int DrawType2(){ // Der Typ des Zeichens des zweiten Puffers
   return(m_drawtype2);
}  

string Help(){ // Der Tipp für die Verwendung der Parameter
   return(m_help);
}

int Digits(){ // die Anzahl der Zeichen nach dem Komma beim Indikator<
   return(m_digits);
}

int LevelsTotal(){ // Die Anzahl der Indikator-Ebene
   return(m_levels_total);
}

double LevelValue(int index){ // Die Erhaltung des Wertes nach dem Index
   return(m_level_value[index]);
}

Alle diese Methoden geben die Werte der entsprechenden Variable zurück, und der Wert der Variablen wird in den Tochterklassen des Oszillators verliehen.  

Die Tochterklassen Calculate

Wir werden zwei Tochter-Klassen erstellen: für die Indikatoren mit einem Puffer und für die Indikatoren mit zwei Puffern. Für die Indikatoren mit einem Puffer:

class COscUni_Calculate1:public COscUni{
   public:
      void COscUni_Calculate1(){
         m_bufferscnt=1;
      }
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & buffer0[],
                     double & buffer1[]
      ){
        
         int cnt,start;
        
         if(prev_calculated==0){
            cnt=rates_total;
            start=0;
         }
         else{
            cnt=rates_total-prev_calculated+1;
            start=prev_calculated-1;
         }  
        
         if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
            return(0);
         }
        
         for(int i=start;i<rates_total;i++){
            buffer1[i]=EMPTY_VALUE;
         }        
        
         return(rates_total);
      }
};

Wir betrachten diese Klasse. Die Klasse hat den Konstrukteur "COscUni_Calculate1", im Konstrukteur wird die Anzahl der Puffer festgesetzt, in diesem Fall 1 festgestellt. In der Methode Calculate(), je nach den Werten der Variablen rates_total und prev_calculate, wird die Anzahl der Elemente des Puffers für das Kopieren (variabel cnt) und den Index der Bar berechnet, von der man den zweiten Puffer (variabel start) reinigen muss. Im Falle des fehlerhaften Kopierens der Daten (beim Aufruf der Funktion CopyBuffer()) aus der Methode kehrt 0 zurück, damit alle Berechnungen von Anfang an auf dem folgenden Tick erfüllt werden. Am Ende der Methode wie üblich kehrt rates_total zurück.

Die Tochterklasse für die Indikatoren mit zwei Puffern:

class COscUni_Calculate2:public COscUni{
   public:
      void COscUni_Calculate2(){
         m_bufferscnt=2;
      }  
      virtual int Calculate( const int rates_total,
                     const int prev_calculated,
                     double & buffer0[],
                     double & buffer1[]
      ){
         int cnt;
         if(prev_calculated==0){
            cnt=rates_total;
         }
         else{
            cnt=rates_total-prev_calculated+1;
         }          
         if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
            return(0);
         }
         if(CopyBuffer(m_handle,1,0,cnt,buffer1)<=0){
            return(0);
         }
         return(rates_total);
      }
};

Diese Klasse ist noch einfacher, als die Klasse für die Indikatoren mit einem Puffer. Am Anfang der Methode Calculate() wird die Anzahl der Elemente (Die Variabel cnt) für das Kopieren berechnet und es wird das Kopieren der Puffer durchgeführt. 

Die Tochterklassen der Indikatoren

Jetzt werden wir die Tochterklassen direkt für Oszillators erstellen. Diese Klassen werden Tochterklassen für die Klasse COscUni_Calculate1 oder COscUni_Calculate2 sein. In diesen Klassen wird nur pro einem Konstrukteur sein. Im Konstrukteur jeder Klasse werden die Parameter übergeben, die direkt dem Oszillator dieser Klasse entsprechen, und noch ein paar zusätzliche Parameter am Anfang. Die zusätzlichen Parameter werden bestimmen, ob die übergebenen Werte der Parameter im Konstrukteur verwendet werden oder ob die Werte standardmäßig installieren (Die Variabel unter dem Namen use_default). Der zweite Parameter unter dem Namen keep_previous bestimmt, ob die Werte standardmäßig nach allen Parametern des Indikators installiert werden oder nur nach denjenigen, die überhaupt nicht verwendet wurden.

Der erste Indikator nach der Liste — ATR, werden wir beginnen, die Tochterklasse für ihn zu schreiben. Erstens die Schablone der Klasse:

class COscUni_ATR:public COscUni_Calculate1{
   public:
   void COscUni_ATR(bool use_default,bool keep_previous,int & ma_period){

   }
};

Bitte beachten Sie: der Parameter ma_period wird über den Link übergeben, damit der Indikator der Parameter standardmäßig im erstellten universellen Oszillator den Zugang auf ihre Werte hat.

Wir schreiben den Code im Konstrukteur:

if(use_default){
   if(keep_previous){
      if(ma_period==-1)ma_period=14;
   }
   else{
      ma_period=14;
   }      
}  

In diesem Teil des Codes, wenn use_default=true ist, wird die Installierung der Werte standardmäßig durchgeführt. Wenn es keep_previous=true ist, die standardmäßigen Werte nur dann installiert, wenn der bekommende Parameter den Wert -1 hat, das heißt, er wurde früher noch nicht verwendet. Entsprechend, bei der Initialization des universellen Oszillators muss man allen Variablen für die Parameter den Wert -1 verleihen.

Jetzt ist es eine die Hauptzeile des Codes im Konstrukteur der Tochterklasse — die Ladung des Indikators:

m_handle=iATR(Symbol(),Period(),ma_period);

Am Ende einige Zeilen für die Installierung der Parameter der Abbildung:

m_name=StringFormat("ATR(%i)",ma_period); // Der Name des Indikators
m_label1="ATR"; // Der Name des Puffers
m_drawtype1=DRAW_LINE;   // Der Typ des Zeichens
m_help=StringFormat("ma_period - Period1(%i)",ma_period); // Der Tipp  
m_digits=_Digits+1; // die Anzahl der Zeichen nach dem Komma beim Wert  
m_levels_total=0; // Die Anzahl der Ebene    

Wir betrachten einige Etappen der Erstellung der Tochterklasse für einen komplizierten Indikator — für MACD. Das Prinzip der Erstellung ist dasselbe, nur der Code wird etwas größer. Deshalb betrachten wir die Fragmente. Die Installierung der Parameter standardmäßig:

if(use_default){
   if(keep_previous){
      if(fast_ema_period==-1)fast_ema_period=12;
      if(slow_ema_period==-1)slow_ema_period=26;
      if(signal_period==-1)signal_period=9;
      if(applied_price==-1)applied_price=PRICE_CLOSE;            
   }
   else{
      fast_ema_period=12;
      slow_ema_period=26;
      signal_period=9;
      applied_price=PRICE_CLOSE;
   }      
}

Die Installierung der Abbildung-Parameter:

m_handle=iMACD(Symbol(),
               Period(),
               fast_ema_period,
               slow_ema_period,
               signal_period,
               (ENUM_APPLIED_PRICE)applied_price);

m_name=StringFormat( "iMACD(%i,%i,%i,%s)",
                     fast_ema_period,
                     slow_ema_period,
                     signal_period,
                     EnumToString((ENUM_APPLIED_PRICE)applied_price));
                    
m_label1="Main";
m_label2="Signal";      
m_drawtype1=DRAW_HISTOGRAM;            
m_drawtype2=DRAW_LINE;

m_help=StringFormat( "fast_ema_period - Period1(%i), "+
                     "slow_ema_period - Period2(%i), "+
                     "signal_period - Period3(%i), "+
                     "applied_price - Price(%s)",
                     fast_ema_period,
                     slow_ema_period,
                     signal_period,
                     EnumToString((ENUM_APPLIED_PRICE)applied_price));  
                    
m_digits=_Digits+1;

Betrachten wir in Details die Parameter des Konstrukteurs:

void COscUni_MACD(bool use_default,
                  bool keep_previous,
                  int & fast_ema_period,
                  int & slow_ema_period,
                  int & signal_period,
                  long & applied_price
){

Geben Sie Acht bitte, die Variable applied_price für die standardmäßige Aufzählung ENUM_APPLIED_PRICE ist wie long erklärt worden. Das wurde gemacht, um die Möglichkeit zu haben, der Variable den Wert -1 zu verleihen, was bedeutet, dass der Parameter noch nicht verwendet wurde.

Wir betrachten noch ein Fragment aus der Klasse für den Indikator RSI — der Teil des Codes, in dem die Installierung der Ebene gemacht wird: 

m_levels_total=3;
ArrayResize(m_level_value,3);
m_level_value[0]=30;
m_level_value[1]=50;
m_level_value[2]=70;

Es wird die Anzahl der Ebene gesetzt, es wird der Umfang des Arrays verändert, und er wird mit den Werten der Ebene ausgefüllt.

Die Beschreibung, wie die Klassen der übrigen Oszillators entstanden sind, betrachten wir hier nicht detailliert. Im Anhang zum Artikel gibt es die vollständige fertige Klasse mit dem vollen Satz der Oszillators (der Name der Datei "CUniOsc.mqh").

Die Erstellung des universellen Oszillators (der Anfang)

Da die Klassen der Oszillators fertig sind, kann man mit ihrer Verwendung schon einen universellen Oszillator erstellen, aber solange noch ohne graphisches Interface.

Erstellen Sie einen neuen Indikator, deren Name "iUniOsc" sein wird. Dann wählen Sie im Meister der Erstellung des Indikators den Typ der Funktion OnCalculate (... open, high, low, close), erstellen Sie eine äußerliche Variable (um später den Platz für äußerliche Variablen zu finden) und zwei Puffer des Typs Line.

Vor der äußerlichen Variablen schließen Sie die Dateien mit der Aufzählung und mit den Klassen der Oszillators an: 

#include <UniOsc/UniOscDefines.mqh>
#include <UniOsc/CUniOsc.mqh>

Erstellen Sie die äußerliche Variable für die Auswahl des Typs des Oszillators:

input EOscUnyType          Type        =  OscUni_ATR;

Die Variablen UseDefault und KeepPrevious:

input bool                 UseDefault  =  true;
input bool                 KeepPrev    =  true;

Die universellen Variablen, die direkt für die Parameter der Oszillators sind:

input int                  Period1     =  14;
input int                  Period2     =  14;
input int                  Period3     =  14;
input ENUM_MA_METHOD       MaMethod    =  MODE_EMA;
input ENUM_APPLIED_PRICE   Price       =  PRICE_CLOSE;  
input ENUM_APPLIED_VOLUME  Volume      =  VOLUME_TICK;  
input ENUM_STO_PRICE       StPrice     =  STO_LOWHIGH;

Einige Indikatoren zeichnen eine Linie, einige — zwei. Der erste Puffer wird manchmal durch eine Linie dargestellt, und manchmal vom Histogramm. Es wäre besser, wenn der Puffer in Form von einer hellen Linie wäre, und das Histogramm grau wäre, deshalb werden wir drei Variable für diese Farbe erstellen:

input color                ColorLine1  =  clrLightSeaGreen;
input color                ColorLine2  =  clrRed;
input color                ColorHisto  =  clrGray;

Da die Erstellung des graphischen Interfaces geplant wird, kann man ohne Neu Start des Indikators den Typ des Oszillators und den Wert der Parameter wechseln, deshalb erstellen wir die Duplikate der Variable Type und der Variablen für die Parameter: 

int                  _Period1;
int                  _Period2;
int                  _Period3;
long                 _MaMethod;
long                 _Price;  
long                 _Volume;  
long                 _StPrice;
EOscUnyType          _Type;

Wir erklären den Variabel-Anzeiger für das Objekt des universellen Oszillators:

COscUni * osc;

Wir erklären noch ein paar Variable:

string ProgName;
string ShortName;

Diese Variablen werden für die Bildung des Indikator-Namens nützlich sein, der im linken oberen Winkel des Unterfensters dargestellt wird. 

Jetzt werden wir den Code ins Ende der Funktion OnInit() hinzufügen, aber zuerst werden wir die Vorarbeit erfüllen. Wir werden die Parameter der Oszillators entsprechend den Werten der Variablen UseDefault und KeepPrevious vorbereiten (auch werden wir den Wert der Variable _Type verleihen), wir werden es in Form einer Funktion aufzeichnen, damit der Code bequem strukturiert ist:

void PrepareParameters(){

   _Type=Type;

   if(UseDefault && KeepPrev){
      _Period1=-1;
      _Period2=-1;
      _Period3=-1;
      _MaMethod=-1;
      _Volume=-1;
      _Price=-1;  
      _StPrice=-1;
   }
   else{  
      _Period1=Period1;
      _Period2=Period2;
      _Period3=Period3;
      _MaMethod=MaMethod;
      _Volume=Volume;
      _Price=Price;  
      _StPrice=StPrice;
   }
}

Wenn UseDefault und KeepPrevious verwendet werden, werden den ganzen Variablen der Wert -1 verliehen, damit die Variablen im Konstrukteur der Klasse zu unterscheiden, die wir noch nicht verwendet haben, und für sie die Werte standardmäßig installieren. Für die übrigen Fällen werden die Werte aus dem Fenster der Eigenschaften verliehen, die entweder verwendet werden, wie sie sind oder werden auf die standardmäßigen Werte der Bildung des Objektes ausgewechselt.  

Nach der Vorbereitung der Parameter werden wir den gewählten Oszillator laden. Der Code der Ladung ist in Form der Funktion aufgemacht, hier wird ihr Fragment aufgeführt:

void LoadOscillator(){
   switch(_Type){
      case OscUni_ATR:
         osc=new COscUni_ATR(UseDefault,KeepPrev,_Period1);
      break;
      case OscUni_BearsPower:
         osc=new COscUni_BearsPower(UseDefault,KeepPrev,_Period1);
      break;
      case OscUni_BullsPower:
         osc=new COscUni_BullsPower(UseDefault,KeepPrev,_Period1);
      break;      
      ...
   }  
}

Nach der Ladung des Oszillators überprüfen wir Handel:

if(!osc.CheckHandle()){
   Alert("Fehler bei der Ladung des Indikators "+osc.Name());
   return(INIT_FAILED);
}

Wenn die Ladung erfüllt ist, stellen wir die Stile des Zeichnens ein, die wir durch die entsprechenden Methoden des Objektes bekommen. Dieser Teil des Codes wurde auch in Form der Funktion erstellt, der Code wird vollständig aufgeführt:

void SetStyles(){  

   //Die Einstellung der Stilen
   if(osc.BuffersCount()==2){
      PlotIndexSetInteger(0,PLOT_DRAW_TYPE,osc.DrawType1());
      PlotIndexSetInteger(1,PLOT_DRAW_TYPE,osc.DrawType2());
      PlotIndexSetInteger(0,PLOT_SHOW_DATA,true);
      PlotIndexSetInteger(1,PLOT_SHOW_DATA,true);
      PlotIndexSetString(0,PLOT_LABEL,osc.Label1());
      PlotIndexSetString(1,PLOT_LABEL,osc.Label2());
      if(osc.DrawType1()==DRAW_HISTOGRAM){
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorHisto);
      }
      else{
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorLine1);        
      }
      PlotIndexSetInteger(1,PLOT_LINE_COLOR,ColorLine2);  
   }
   else{
      PlotIndexSetInteger(0,PLOT_DRAW_TYPE,osc.DrawType1());
      PlotIndexSetInteger(1,PLOT_DRAW_TYPE,DRAW_NONE);  
      PlotIndexSetInteger(0,PLOT_SHOW_DATA,true);
      PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);  
      PlotIndexSetString(0,PLOT_LABEL,osc.Label1());
      PlotIndexSetString(1,PLOT_LABEL,"");
      if(osc.DrawType1()==DRAW_HISTOGRAM){
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorHisto);
      }
      else{
         PlotIndexSetInteger(0,PLOT_LINE_COLOR,ColorLine1);        
      }        
   }
  
   // Die Einstellung digits
   IndicatorSetInteger(INDICATOR_DIGITS,osc.Digits());

   // Die Einstellung der Ebenen
   int levels=osc.LevelsTotal();
   IndicatorSetInteger(INDICATOR_LEVELS,levels);
   for(int i=0;i<levels;i++){
      IndicatorSetDouble(INDICATOR_LEVELVALUE,i,osc.LevelValue(i));
   }

}    

Erstens, je nach der Zahl der Oszillator-Puffer wird eine von zwei Varianten der Installierung der Stile durchgeführt. Dabei, wenn der erste Puffer — ein Histogramm ist, wird ein entsprechender Typ des Puffers gesetzt. Danach wird die Anzahl der Zeichen nach dem Komma beim Wert des Indikators gesetzt. Am Ende werden die Ebene gesetzt.

Unten wurde der volle Code der Funktion OnInit() aufgeführt, mit dem Aufruf der gerade erstellten Funktion:

int OnInit(){

   SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
   SetIndexBuffer(1,Label2Buffer,INDICATOR_DATA);

   PrepareParameters();

   LoadOscillator();
  
   if(!osc.CheckHandle()){
      Alert("Fehler bei der Ladung des Indikators "+osc.Name());
      return(INIT_FAILED);
   }

   SetStyles();
  
   Print("Parameters matching: "+osc.Help());
  
   ShortName=ProgName+": "+osc.Name();  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);
  
   return(INIT_SUCCEEDED);
}

Bitte beachten Sie: Am Ende der Funktion wird der Aufruf der Funktion Print mit dem Hinweis über die verwendeten Parameter des Fensters der Eigenschaften durchgeführt, dann wird der kurze Name des Indikators gesetzt.

Darauf beendet der erste Teil der Arbeit, in dem der universelle Oszillator vollständig gebildet wird. Es wurde ein Indikator bekommen, bei dessen Hilfe man die früher erstellten Klassen überprüfen kann. Weiter werden wir die Klasse des graphischen Interfaces erstellen.

Im Anhang zum Artikel gibt es den vollständig fertigen Indikator mit dem Namen "iUniOsc" (später wird in seinem Kode die unbedeutende Veränderung beigetragen, und er wird sich vom Indikator ein wenig unterscheiden, der in der gegebenen Etappe bekommen ist.) 

Der Plan der Erstellung des graphischen Interfaces

Für die Bildung des graphischen Interfaces kann man einfach die graphischen Objekte verwenden: für die Einführung der Zahlenwerte wird das graphische Objekt "das Feld der Einführung" verwendet, für die Parameter der Typ der Aufzählungen (die auftauchenden Listen) — nach einigen Knöpfen. Jedoch wird diese Methode sehr arbeitsintensiv. Zum gegenwärtigen Augenblick wurden auf MQL5 einige Bibliotheken für die Erstellung des graphischen Interfaces geschrieben. Die Bibliotheken ermöglichen die standardmäßigen Steuerungselemente zu erstellen: die Dialogfenster, das Feld der Einführung mit den Knöpfen für die Vergrößerung und die Verkleinerung der Werte (Spinbox), die auftauchenden Listen und anderes. Zum Satz des Terminals gehört der Satz der standardmäßigen Klassen für die Erstellung der Panels und der Dialoge. Im Titel "Artikels" gibt es eine riesige Reihe der Artikel, die der Erstellung des graphischen Interfaces gewidmet sind.

Auch gibt es in den Artikeln eine kleine Reihe aus drei Publikationen (der Artikel 1, der Artikel 2, der Artikel 3), die der sehr einfachen und schnellen Erstellung der graphischen Interfaces gewidmet sind. Außer der Betrachtung der Theorie wird in diesen Artikeln die Bibliothek für die schnelle Arbeit mit den graphischen Objekten und für die Erstellung des graphischen Interfaces erstellt. Alle oben genannten Varianten haben die Vorteile und die Nachteile, alle diesen wurden bei der Schreibung des vorliegenden Artikels sehr detailliert betrachtet. Es wurde die letzte Variante aus Aufgezählten (die Bibliothek incGUI) gewählt.

Das Terminal MetaTrader 5 entwickelt sich sehr aktiv und vervollkommnt sich, deshalb können einige Steuerungselemente aus der angebotenen Bibliothek als moralisch veraltend (insbesondere die Streifen des Rollens) gezählt, aber nichtsdestoweniger kann man sie benutzen. Um zu beginnen, die Bibliothek zu verwenden, laden Sie die Anwendung zum Artikel "die graphischen benutzerdefinierten Steuerungselemente herunter. Das Teil 3. Die Formen für MetaTrader 5", packen Sie es aus, die Datei"incGUI_v3.ordnen Sie in den Ordner Include zu, die im Ordner der Daten des Terminals geordnet ist.

Die Klasse der Form

Die ganze Arbeit nach der Erstellung des graphischen Interfaces werden wir in der separaten Datei "UniOscGUI.mqh" durchführen. Vor allen Dingen werden wir die Bibliothek anschließen:

#include <IncGUI_v3.mqh>

Wir werden die Kompilation für die Prüfung durchführen. Bei der Kompilation werden ein paar Warnungen gegeben, über die der Kompilator früher nicht berichtete. Jetzt ermöglicht der vervollkommnte Kompilator diese Nachteile des Codes herauszufinden und zu korrigieren. Im Anhang zum Artikel kann man die korrigierte Datei "inc_GUI_v4" finden. Statt "IncGUI_v3.mqh" werden wir "IncGUI_v4.mqh" und "UniOscDefines.mqh" anschließen. 

#include <IncGUI_v4.mqh>
#include <UniOsc/UniOscDefines.mqh>

Wir werden die Kopie des Indikators "iUniOsc" unter dem Namen "iUniOscGUI" speichern. Danach kann der Indikator "iUniOsc" ein wenig editiert werden — und die Parameter UseDefault und KeepPrev können verborgen werden. Im Indikator ohne graphisches Interface sind sie sinnlos, aber man muss von ihm die Werte false setzen:

bool                 UseDefault  =  false;
bool                 KeepPrev    =  false;

In dem Punkt wird der Indikator "iUniOsc" vollständig beendet.

Wir werden die Arbeit mit dem Indikator "iUniOscGUI" fortsetzen. Fügen wir zu ihm die Datei "UniOscGUI.mqh" hinzu. Insgesamt sollen drei Dateien hinzugefügt werden:

#include <UniOsc/UniOscDefines.mqh>
#include <UniOsc/CUniOsc.mqh>
#include <UniOsc/UniOscGUI.mqh>

Bei dem Kompilieren des Indikators kann man den Code prüfen, und zugleich auf dem Chart das graphische Interface sehen. Aber solange die ganze Arbeit noch in der Datei "UniOscGUI.mqh" läuft. 

Das graphische Interface wird ein Dialogfenster darstellen, in dessen Oberteil sich die auftauchende Liste für die Auswahl des Oszillators befinden wird, und unten — der Satz der Steuerungselemente, der jedem konkreten Oszillator entsprechen. Das bedeutet, in der Datei wird die Klasse für die Erstellung der Form mit den Gruppen der Klassen (elterlich und etwas tochter-) für die Erstellung der Steuerungselemente auf dieser Form sein.

Wir beginnen mit der Form. Die ausführliche schrittweisende Prozedur der Erstellung der Form wurde im Artikel " die graphischen benutzerdefinierten Steuerungselemente dargestellt. Das Teil 3. Die Formen für MetaTrader 5". Hier werden wir diese Prozedur in Bezug auf unsere Aufgabe durchführen.

1. Aus der Datei "IncGUI_v4.mqh" kopieren wir die Klasse CFormTemplate in die Datei "UniOscGUI.mqh" und nennen die als CUniOscForm.

2. Wir setzen die Eigenschaften. Es wird in der Methode MainProperties() der Klasse CUniOscForm gemacht. Wir setzen die folgenden Eigenschaften:

void MainProperties(){
      m_Name         =  "UniOscForm";
      m_Width        =  FORM_WIDTH;
      m_Height       =  150;
      m_Type         =  0;
      m_Caption      =  "UniOsc";
      m_Movable      =  true;
      m_Resizable    =  true;
      m_CloseButton  =  true;
}

Wir beachten, der Variable m_Heigh wird der Wert der Konstante FORM_WIDTH verliehen. In der abschließenden Etappe der Arbeit muss man die passende Größe der Form und der Steuerungselemente auswählen, deshalb fügen wir zum Oberteil der Datei einige Konstanten hinzu:

#define FORM_WIDTH 210        // Die Breite der Form
#define SPIN_BOX_WIDTH 110    // Die Breite der Spinbox
#define COMBO_BOX_WIDTH 110   // Die Breite der auftauchenden Liste

Danach kann man die Form im Indikator verwenden. Im Indikator werden wir die äußerliche Variable UseGUI mit dem standardmäßigen Wert true (gleich am Anfang des Fensters der Eigenschaften) erklären:

input bool                 UseGUI      =  true;

Nach den äußerlichen Variablen erklären wir diese Variable — der Anzeiger für die Klasse der Form:

CUniOscForm * frm;

In der Funktion OnInit() des Indikators, wenn der Wert der Variable UseGUI gleich true ist, werden wir das Objekt erstellen und bereiten ihn zur Verwendung durch den Aufruf der notwendigen Methoden für die Setzung der zusätzlichen Eigenschaften auf:

frm=new CUniOscForm();  // Die Erstellung des Objektes
frm.Init();             // die Initialisierung
frm.SetSubWindow(0);    // die Setzung des Unterfensters, in dem die Form dargestellt wird
frm.SetPos(10,30);      // die Setzung der Anfangsposition der Form
frm.Show();             // das Hinzufügen der Sichtbarkeit der Form

In der Funktion OnDeinit() verbergen und entfernen wir das Objekt:

if(CheckPointer(frm)==POINTER_DYNAMIC){
   frm.Hide();
   delete(frm);
}

Aus der Funktion OnChartEvent() rufen wir die Methode Event() auf:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   frm.Event(id,lparam,dparam,sparam);  
}

Wenn der Indikator nun zum Chart hinzugefügt wird, kann man die Form sehen (die Abb. 2).


in Abb. 2. Die Form, die von der Klasse CUniOscForm beim Hinzufügen des Indikators iUniOscGUI zum Chart erstellt wurde  


Alle Knöpfe der Form gelten dafür: die Form kann man mit Hilfe des Knopfes im linken oberen Winkel bewegen (drücken auf den Knopf und die neue Stelle der Form durch den Klick auf die Maus geben), man kann es zusammenrollen(der Knopf mit dem Rechteck im rechten oberen Winkel). Beim Druck auf den Knopf mit dem Kreuzchen wird die Form geschlossen, man muss dabei den Indikator vom Chart entfernen. Für die Entfernung des Indikators vom Chart wird die Funktion ChartIndicatorDelete() verwendet. Um diese Funktion zu verwenden, muss man die Unterfenster-Nummer des Indikators wissen, welches man durch die Funktion ChartWindowFind() aufklären kann, und dafür muss der kurze Name des Indikators verwendet werden.

Beim Druck auf den Knopf der Form-Schließung gibt die Methode Event() den Wert 1 zurück. Wir werden den zurückgegebenen Wert überprüfen und notfalls werden wir den Indikator vom Chart entfernen:

int win=ChartWindowFind(0,ShortName);  // Die Suche des Unterfensters
ChartIndicatorDelete(0,win,ShortName); // Die Entfernung des Indikators
ChartRedraw();                         // Die Beschleunigung des Abzeichnens des Charts

Jetzt wird beim Druck auf den Knopf mit dem Kreuzchen nicht nur die Form-Schließung durchgeführt, sondern auch die Entfernung des Indikators vom Chart.  

Wir werden zur Form das Hauptelement der Steuerung hinzufügen: die auftauchende Liste für die Auswahl des Oszillatorstyps. Für seine Erstellung wird die Klasse CComBox verwendet. Wir fügen den Code zur Klasse CUniOscForm hinzu. Wir erklären die Variable für das Objekt:

CComBox m_cmb_main;

Dann werden wir in der Methode OnInitEvent() die Methode Init() der Klasse aufrufen:

m_cmb_main.Init("cb_main",100," select oscillator");

In die Methode wird der Name des Steuerungselementes (das Präfix für die Namen der graphischen Objekte), die Breite des Steuerungselementes und die Aufschrift neben ihm übergeben. 

In der Methode OnShowEvent() werden wir die Methode Show() aufrufen:

m_cmb_main.Show(aLeft+10,aTop+10);

Beim Aufruf der Methode werden die Koordinaten der Anordnung des Elementes auf der Form bezeichnet(mit dem Einzug 10 Pixels vom linken oberen Winkel des benutzerdefinierten Raumes der Form). 

Zur Methode OnHideEvent() werden wir die Methode Hide() aufrufen:

m_cmb_main.Hide();

Nach dem Ereignis der Veränderung der Auswahl in der Hauptliste muss man einen anderen Indikator laden. Es wird bequemer in der Datei des Indikators machen, deshalb die Methode Event() der Liste der Oszillatoren werden wir nicht aus der Methode EventsHandler() der Form aufrufen, sondern aus der Funktion OnChartEvent() des Indikators, eben hier werden wir dieses Ereignis bearbeiten:

int me=frm.m_cmb_main.Event(id,lparam,dparam,sparam);
if(me==1){
   Alert(frm.m_cmb_main.SelectedText());
}  

In die Methode werden die standardmäßigen Parameter des Ereignisses des Charts übergeben, und bei der Zurücksetzung des Wertes von der Methode wird das Nachricht-Fenster geöffnet.

Man muss die Liste mit den Varianten ausfüllen. Hier können verschiedene Vorgehensweise benutzt werden:

  • Allen in der Methode OnInitEvent()zu machen die Formen
  • zur Klasse der Form die zusätzlichen Methode hinzuzufügen und das aus dem Indikator nach der Methode Init() aufrufen
  • Die Methoden der Liste unmittelbar aus dem Indikator aufzurufen.

Wir verwenden die dritte Variante (er fordert die kleinste Anzahl des Codes) verwenden. Erstens werden wir im Indikator ein Arrays mit den Varianten des Oszillators erstellen:

EOscUniType osctype[]={
   OscUni_ATR,
   OscUni_BearsPower,
   OscUni_BullsPower,  
   OscUni_CCI,
   OscUni_Chaikin,
   OscUni_DeMarker,
   OscUni_Force,
   OscUni_Momentum,
   OscUni_MACD,
   OscUni_OsMA,
   OscUni_RSI,
   OscUni_RVI,
   OscUni_Stochastic,
   OscUni_TriX,
   OscUni_WPR
};

Dann, nach dem Aufruf frm. Init() im Indikator werden wir die Liste ausfüllen und wir werden den als standardmäßig gewählten Punkt setzen:

for(int i=0;i<ArraySize(osctype);i++){
   frm.m_cmb_main.AddItem(EnumToString(osctype[i]));
}
frm.m_cmb_main.SetSelectedIndex(0);

In dieser Etappe kann man die Prüfung durchführen. In der Form soll die auftauchende Liste mit den Typen der Oszillatoren dargestellt werden, und bei der Veränderung des gewählten Punktes soll sich das Fenster mit dem entsprechenden Text öffnen (die Abb. 3):

 
in Abb. 3. Die Form mit der Liste der Oszillatoren und das Nachricht-Fenster nach der Veränderung der Auswahl in der Liste 

Die Steuerelemente auf der Form

Am Anfang des Artikels wurde das Maximum der äußerlichen Parameter nach den Typen bestimmt (drei für die Einführung der Zahlenwerte und vier für die standardmäßigen Aufzählungen). Für die Einführung der Zahlenwerte verwenden wir das Element CSpinInputBox (das Feld der Einführung mit den Knöpfen) der Bibliotheken incGUI, für die standardmäßigen Aufzählungen — das Element CComBox (die auftauchende Liste).

Am Anfang der Datei mit der Klasse des graphischen Interfaces werden wir die Arrays mit den Werten der standardmäßigen Aufzählungen erklären:

ENUM_APPLIED_PRICE e_price[]={   PRICE_CLOSE,
                                 PRICE_OPEN,
                                 PRICE_HIGH,
                                 PRICE_LOW,
                                 PRICE_MEDIAN,
                                 PRICE_TYPICAL,
                                 PRICE_WEIGHTED
};

ENUM_MA_METHOD e_method[]={MODE_SMA,MODE_EMA,MODE_SMMA,MODE_LWMA};

ENUM_APPLIED_VOLUME e_volume[]={VOLUME_TICK,VOLUME_REAL};

ENUM_STO_PRICE e_sto_price[]={STO_LOWHIGH,STO_CLOSECLOSE};

In der Klasse der Form werden wir Variablen für die Steuerungselemente (drei Typen CSpinInputBox und vier CComBox) erklären:

CSpinInputBox m_value1;
CSpinInputBox m_value2;      
CSpinInputBox m_value3;
      
CComBox m_price;
CComBox m_method;
CComBox m_volume
CComBox m_sto_price;

In der Klasse der Form, in der Methode OnInitEvent() initialisiern wir die auftauchenden Listen (die Objekte der Klasse CComBox) und ausfüllen sie durch die früher erklärten Arrays:

m_price.Init("price",COMBO_BOX_WIDTH," price");
m_method.Init("method",COMBO_BOX_WIDTH," method");
m_volume.Init("volume",COMBO_BOX_WIDTH," volume");
m_sto_price.Init("sto_price",COMBO_BOX_WIDTH," price");              

for(int i=0;i<ArraySize(e_price);i++){
   m_price.AddItem(EnumToString(e_price[i]));            
}
for(int i=0;i<ArraySize(e_method);i++){
   m_method.AddItem(EnumToString(e_method[i]));              
}            
for(int i=0;i<ArraySize(e_volume);i++){
   m_volume.AddItem(EnumToString(e_volume[i]));            
}            
for(int i=0;i<ArraySize(e_sto_price);i++){
   m_sto_price.AddItem(EnumToString(e_sto_price[i]));            
}

Da für verschiedene Indikatoren auf der Form verschiedene Sätze der Steuerungselemente dargestellt werden sollen, werden wir die Klassen (grundlegenden und tochter) für die Bildung der Sätze erstellen. Der Name der grundlegenden Klasse CUniOscControls, unten wird seine Schablone aufgeführt:

class CUniOscControls{
   protected:
      CSpinInputBox * m_value1;
      CSpinInputBox * m_value2;      
      CSpinInputBox * m_value3;
      CComBox * m_price;
      CComBox * m_method;
      CComBox * m_volume;
      CComBox * m_sto_price;
   public:
   void SetPointers(CSpinInputBox & value1,
                        CSpinInputBox & value2,      
                        CSpinInputBox & value3,
                        CComBox & price,
                        CComBox & method,
                        CComBox & volume,
                        CComBox & sto_price){
       ...
   }
   void Hide(){
       ...
   }
   int Event(int id,long lparam,double dparam,string sparam){
      ...
      return(0);
   }
   virtual void InitControls(){
   }  
   virtual void Show(int x,int y){
   }  
   virtual int FormHeight(){
      return(0);
   }
};

Am Anfang der Verwendung des Objektes der gegebenen Klasse wird die Methode SetPointers() aufgerufen, in die Methode werden die Anzeiger auf alle Steuerungselemente übergeben, und in der Methode werden sie in den eigenen Variablen der Klasse gespeichert: 

void SetPointers(CSpinInputBox & value1,
                     CSpinInputBox & value2,      
                     CSpinInputBox & value3,
                     CComBox & price,
                     CComBox & method,
                     CComBox & volume,
                     CComBox & sto_price){
   m_value1=GetPointer(value1);
   m_value2=GetPointer(value2);      
   m_value3=GetPointer(value3);            
   m_price=GetPointer(price);
   m_method=GetPointer(method);
   m_volume=GetPointer(volume);
   m_sto_price=GetPointer(sto_price);
}

Durch diese Anzeiger wird die Verschleierung aller Steuerungselemente durchgeführt (die Methode Hide()):

void Hide(){
   m_value1.Hide();
   m_value2.Hide();
   m_value3.Hide();
   m_price.Hide();
   m_method.Hide();
   m_volume.Hide();
   m_sto_price.Hide();
}

Es werden ihre Ereignisse verarbeitet (die Methode Event()):

int Event(int id,long lparam,double dparam,string sparam){
   int e1=m_value1.Event(id,lparam,dparam,sparam);
   int e2=m_value2.Event(id,lparam,dparam,sparam);
   int e3=m_value3.Event(id,lparam,dparam,sparam);
   int e4=m_price.Event(id,lparam,dparam,sparam);
   int e5=m_method.Event(id,lparam,dparam,sparam);
   int e6=m_volume.Event(id,lparam,dparam,sparam);
   int e7=m_sto_price.Event(id,lparam,dparam,sparam);
   if(e1!=0 || e2!=0 || e3!=0 || e4!=0 || e5!=0 ||e6!=0 || e7!=0){
      return(1);
   }
   return(0);
}

Die übrigen Methoden sind virtuell, für jeden Oszillator wird der eigene Code in den Tochterklassen erstellt. Die Methode Show() wird für die Darstellung der Steuerungselemente verwendet. Die Methode FormHeight() wird die Höhe der Form zurückbringen. Die Methode InitControls() ist nur für den Wechsel der Aufschriften neben den Steuerungselementen vorgesehen(die Abb. 4).


in Abb. 4. Die verschiedenen Aufschriften neben dem einem und desselben des Steuerungselements bei verschiedenen Oszillatoren 

Es handelt sich darum, dass die Steuerungselemente aus der Bibliothek incGUI nur die minimal notwendigen Methoden-Sätze haben und sie haben keine Methoden für die Veränderung der Aufschriften. Aber die Klassen wurden so entwickelt, dass man notfalls die Aufschrift ändern kann, durch den Aufruf der Methode Init(). Da der Wechsel der Aufschrift von der Methode Init() durchgeführt wird, wird die Methode InitControls() genannt.  

Wir betrachten einige Tochterklassen. Das einfachste von ihnen — für den Indikator ATR, das komplizierteste — für Stochastic.

für ATR:

class CUniOscControls_ATR:public CUniOscControls{
   void InitControls(){
      m_value1.Init("value1",SPIN_BOX_WIDTH,1," ma_period");
   }
   void Show(int x,int y){
      m_value1.Show(x,y);
   }  
   int FormHeight(){
      return(70);
   }  
};

In der Methode InitContrlos() wird der Aufruf der Methode Init() des Steuerungselements durchgeführt, das Wichtigste (wofür man diese virtuelle Methode machen musste) — es wird der Text der Aufschrift "ma_period" übergeben, der rechts vom Steuerungselement dargestellt wird.

In der Methode Show() der Klasse der Form wird der Aufruf der Methode Show() der Klasse CUniOscControls durchgeführt, beim Aufruf werden die Koordinaten des oberen linken Winkels erstes (ober) des Steuerungselements bezeichnet. Die Methode FormHeight() liefert den Wert einfach zurück.

für Stochastic:

class CUniOscControls_Stochastic:public CUniOscControls{
   void InitControls(){
      m_value1.Init("value1",SPIN_BOX_WIDTH,1," Kperiod");
      m_value2.Init("value2",SPIN_BOX_WIDTH,1," Dperiod");  
      m_value3.Init("value3",SPIN_BOX_WIDTH,1," slowing");          
   }
   void Show(int x,int y){
      m_value1.Show(x,y);
      m_value2.Show(x,y+20);      
      m_value3.Show(x,y+40);
      m_method.Show(x,y+60);      
      m_sto_price.Show(x,y+80);
   }
   int FormHeight(){
      return(150);
   }    
};

In der Methode Show() wird die Berechnung der Koordinaten für jedes Steuerungselement durchgeführt, das ganze Übrige sollte schon klar sein.

Endlich betrachten wir unmittelbar ein Hinzufügen der Steuerungselemente zur Form. In der Klasse der Form werden wir die Variabel-Anzeiger auf die Klasse mit den Steuerungselementen erklären:

CUniOscControls * m_controls;

In der Destruktion werden wir das Objekt entfernen:

void ~CUniOscForm(){
   delete(m_controls);
}

Wir werden zur Klasse der Form die Methode SetType() hinzufügen. Diese Methode wird für den Hinweis des verwendeten Oszillators-Typ aufgerufen. 

      void SetType(long type){
         if(CheckPointer(m_controls)==POINTER_DYNAMIC){
            delete(m_controls);
            m_controls=NULL;
         }
        
         switch((EOscUniType)type){
            case OscUni_ATR:
               m_controls=new CUniOscControls_ATR();
            break;
            case OscUni_BearsPower:
               m_controls=new CUniOscControls_BearsPower();
            break;
            case OscUni_BullsPower:
               m_controls=new CUniOscControls_BullsPower();
            break;
            case OscUni_CCI:
               m_controls=new CUniOscControls_CCI();
            break;
            case OscUni_Chaikin:
               m_controls=new CUniOscControls_Chaikin();
            break;
            case OscUni_DeMarker:
               m_controls=new CUniOscControls_DeMarker();
            break;
            case OscUni_Force:
               m_controls=new CUniOscControls_Force();
            break;
            case OscUni_Momentum:
               m_controls=new CUniOscControls_Momentum();
            break;
            case OscUni_MACD:
               m_controls=new CUniOscControls_MACD();
            break;
            case OscUni_OsMA:
               m_controls=new CUniOscControls_OsMA();
            break;
            case OscUni_RSI:
               m_controls=new CUniOscControls_RSI();
            break;
            case OscUni_RVI:
               m_controls=new CUniOscControls_RVI();
            break;
            case OscUni_Stochastic:
               m_controls=new CUniOscControls_Stochastic();
            break;
            case OscUni_TriX:
               m_controls=new CUniOscControls_TriX();
            break;
            case OscUni_WPR:
               m_controls=new CUniOscControls_WPR();
            break;
         }
        
         m_controls.SetPointers(m_value1,m_value2,m_value3,m_price,m_method,m_volume,m_sto_price);
         m_controls.InitControls();
        
         m_value1.SetReadOnly(false);
         m_value2.SetReadOnly(false);
         m_value3.SetReadOnly(false);
        
         m_value1.SetMinValue(1);        
         m_value2.SetMinValue(1);
         m_value3.SetMinValue(1);
        
         m_Height=m_controls.FormHeight();        
        
      }  

Am Anfang der Methode wird die Entfernung des Objektes durchgeführt, wenn er existierte. Dann wird, je nach dem Typ des Indikators, die Ladung der entsprechenden Klasse durchgeführt. Unten werden die Methoden SetPointers() und InitControls() aufgerufen. Dann werden einige zusätzliche Aktionen durchgeführt: für die Steuerungselemente SpinBox wird die Möglichkeit der Einführung von der Tastatur (der Aufruf der Methode ReadOnly()) ergänzt, werden die minimalen Werte (der Aufruf der Methode SetMinValue()) eingesetzt, und der Variable m_Height wird ein neuer Höhe-Wert der Form verliehen.

In den Methoden OnShowEvent() und OnHideEvent(), wir werden die Formen der entsprechenden Methoden des Objektes m_controls aufrufen:

void OnShowEvent(int aLeft, int aTop){
   m_cmb_main.Show(aLeft+10,aTop+10);
   m_controls.Show(aLeft+10,aTop+10+20);
}
void OnHideEvent(){
   m_cmb_main.Hide();            
   m_controls.Hide();          
}  

Es bleibt jetzt, die Ereignisse des Objektes m_controls "zu beleben". In den Indikator in die Funktion OnChartEvent() fügen wir den Aufruf der Methode Event() hinzu:

int ce=frm.m_controls.Event(id,lparam,dparam,sparam);

In OnInit() des Indikators fügen wir den Aufruf der Methode SetType() die Formen (nach dem Aufruf der Methode SetSelectedIndex ()) hinzu:

frm.SetType(_Type);

Nach der Ladung des Oszillators ist es notwendig, dass die Werte auf der Form seiner Parameter dargestellt würden, dazu werden wir zur Klasse der Form die Methode SetValues() hinzufügen:

void SetValues(int period1,
               int period2,
               int period3,
               long method,
               long price,
               long volume,  
               long sto_price  
){
  
   m_value1.SetValue(period1);
   m_value2.SetValue(period2);      
   m_value3.SetValue(period3);
  
   for(int i=0;i<ArraySize(e_price);i++){
      if(price==e_price[i]){
         m_price.SetSelectedIndex(i);
         break;
      }
   }
  
   for(int i=0;i<ArraySize(e_method);i++){
      if(method==e_method[i]){
         m_method.SetSelectedIndex(i);
         break;
      }
   }            

   for(int i=0;i<ArraySize(e_volume);i++){
      if(volume==e_volume[i]){
         m_volume.SetSelectedIndex(i);
         break;
      }
   }
  
   for(int i=0;i<ArraySize(e_sto_price);i++){
      if(sto_price==e_sto_price[i]){
         m_sto_price.SetSelectedIndex(i);
         break;
      }
   }

}      

In der Methode SetValues(), zu den Steuerungselementen als SpinBox werden wir die Werte, wie sie sind, setzen, und für die Aufzählungen wird die Suche des Indexes in den Arrays mit den Werten der Aufzählungen durchgeführt. Wir werden die Methode SetValues() nach dem Aufruf der Methode SetType() aufrufen:

frm.SetValues(_Period1,_Period2,_Period3,_MaMethod,_Price,_Volume,_StPrice);

In dieser Etappe zählt man das graphische Interface vollständig fertig (die Abb. 5), aber nur bis der Indikator auf ihn noch nicht reagiert.


in Abb. 5. Die Art des Fensters mit den Steuerungselementen für den Indikator ATR 

Die Vollendung der der Erstellung des universellen Oszillators

Die Klassen des Oszillators sind fertig, die Klassen des graphischen Interfaces sind auch fertig, es bleibt nur übrig, sie zusammen zu verbinden. 

In der gegebenen Etappe soll die Funktion OnChatEvent() nach folgender Weise aussehen:

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{

   int e=frm.Event(id,lparam,dparam,sparam);  
   if(e==1){
      int win=ChartWindowFind(0,ShortName);
      ChartIndicatorDelete(0,win,ShortName);
      ChartRedraw();
   }
  
   int me=frm.m_cmb_main.Event(id,lparam,dparam,sparam);
  
   int ce=frm.m_controls.Event(id,lparam,dparam,sparam);

}

Man muss das Ereignis des Indikators-Wechsels (Variabel me) und das Ereignis des Wechsels der Parameter (Variabel ce) bearbeiten.

Der Wechsel des Indikators:

if(me==1){

   //Die Neuladung des Indikators
  
   _Type=osctype[frm.m_cmb_main.SelectedIndex()]; // der neu Typ
  
   delete(osc); // Die Löschung des alten Objektes
   LoadOscillator(); // Die Ladung des neuen Indikators
  
   if(!osc.CheckHandle()){
      Alert("Fehler bei der Ladung des Indikators "+osc.Name());
   }
  
   SetStyles(); // Die Einstellung der Stilen
  
   // Die Einstellung des kurzen Namens
   ShortName=ProgName+": "+osc.Name();  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);

   // Die Erneuerung der Form

   frm.SetType(osctype[frm.m_cmb_main.SelectedIndex()]); // Die Einstellung des Typs
   frm.SetValues(_Period1,_Period2,_Period3,_MaMethod,_Price,_Volume,_StPrice); // Die Einstellung des Wertes
   frm.Refresh(); // Die Erneuerung der Form
  
   // Die Neuberechnung des Indikators
   EventSetMillisecondTimer(100);

}  

Lassen Sie uns den Code gründlicher betrachten. Bei der Auswahl des Oszillators in der Hauptliste gibt die Methode Event() 1 zurück, in diesem Fall wird der Variable _Type ein neuer Wert des Typs verliehen, das alte Objekt wird entfernt, es wird ein neues Objekt geladen, werden die Stile und ein kurzer Name gesetzt. Nach der Ladung des Indikators wird die Art der Form erneuert: es wird ein Typ und Parameter gesetzt, und es wird die Methode Refresh() aufgerufen, damit die Art der Form sich entsprechend den neuen Parametern geändert hat. Am Ende wird der Timer gestartet(darüber werden wir ein wenig später reden).

Wir betrachten eine Stelle des Codes für den Wechsel der Parameter: 

if(ce==1){
  
   if((int)frm.m_value1.Value()>0){
      _Period1=(int)frm.m_value1.Value();
   }
   if((int)frm.m_value2.Value()>0){
      _Period2=(int)frm.m_value2.Value();
   }
   if((int)frm.m_value3.Value()>0){
      _Period3=(int)frm.m_value3.Value();
   }      
   if(frm.m_method.SelectedIndex()!=-1){
      _MaMethod=e_method[frm.m_method.SelectedIndex()];
   }
   if(frm.m_price.SelectedIndex()!=-1){
      _Price=e_price[frm.m_price.SelectedIndex()];
   }
   if(frm.m_volume.SelectedIndex()!=-1){
      _Volume=e_volume[frm.m_volume.SelectedIndex()];
   }
   if(frm.m_sto_price.SelectedIndex()!=-1){
      _StPrice=e_sto_price[frm.m_sto_price.SelectedIndex()];
   }
  
   delete(osc);
   LoadOscillator();
   if(!osc.CheckHandle()){
      Alert("Fehler bei der Ladung des Indikators"+osc.Name());
   }  
  
   ShortName=ProgName+": "+osc.Name();  
   IndicatorSetString(INDICATOR_SHORTNAME,ShortName);
  
   EventSetMillisecondTimer(100);
    
}

Bei der Veränderung der Parameter gibt die Methode Event() der Klasse der Steuerungselemente 1 zurück. In diesem Fall werden den ganzen Variablen die neuen Werte verliehen, aber es wird ihre vorläufige Überprüfung durchgeführt. Die Werte der Steuerungselemente SpinBox sollen mehr als Null sein, und die auftauchenden Listen — dürfen nicht -1 gleich sein. Weiter geht alles ebenso, genauso wie beim Wechsel des Indikators.

Jetzt mehr über den Timer. Für die Ausführung der Berechnung des Indikators wird eine bestimmte Zeit gefordert. Deshalb wird der Timer gestartet, und in seiner Funktion wird die periodische Prüfung der Bereitschaft des Indikators mit Hilfe der Funktion BarsCalculated() durchgeführt. Wenn der zurückgegebene Wert mehr als Null ist, bedeutet das, der Indikator wurde schon vollständig berechnet, dabei wird der Aufruf der Methode Calculate() des Objektes osc durchgeführt:

void OnTimer(){
   if(BarsCalculated(osc.Handle())>0){
      if(osc.Calculate(Bars(Symbol(),Period()),0,Label1Buffer,Label2Buffer)!=0){
         ChartRedraw();    
         EventKillTimer();
      }
   }
}

Als der erste Parameter in die Methode Calculate() wird die Anzahl der Bars übergeben, und als der zweite - 0, damit die volle Umrechnung des Indikators stattfindet. Danach wird das Chart (ChartRedaraw()) erneuert und der Timer wird abgeschaltet. 

Jetzt soll der Indikator auf das graphische Interface reagieren. Dies bedeutet, er ist fast fertig.

Wir werden den kleinen letzten Strich ergänzen: wir werden eine Arbeitsmöglichkeit des Indikators ohne graphisches Interface gewährleisten. Dazu werden wir zur äußerlichen Variable UseGUI hinzufügen:

input bool                 UseGUI      =  true;

Den Teil des Codes der Funktion OnInit(), der mit der Erstellung der Form verbunden ist, werden wir durchführen, wenn die Variable UseGUI hinzugefügt ist:

if(UseGUI){
   frm=new CUniOscForm();
   frm.Init();
   int ind=0;
  
   for(int i=0;i<ArraySize(osctype);i++){
      frm.m_cmb_main.AddItem(EnumToString(osctype[i]));
      if(osctype[i]==_Type){
         ind=i;
      }
   }
  
   frm.m_cmb_main.SetSelectedIndex(ind);      
   frm.SetType(_Type);
   frm.SetValues(_Period1,_Period2,_Period3,_MaMethod,_Price,_Volume,_StPrice);
  
   frm.SetSubWindow(0);
   frm.SetPos(10,30);
   frm.Show();
}

Und schließlich noch ein letzter Schritt. Die Bibliothek incGUI unterstützt den Wechsel der Farbenschemen der Steuerungselemente. Wir werden diese Möglichkeit einsetzen.

Wir werden nach den äußerlichen Parametern direkt den folgenden Code ergänzen:

enum eColorScheme{
   DefaultScheme=0,
   YellowBrownScheme=1,
   BlueScheme=2,
   GreenScheme=3,
   YellowBlackScheme=4,
   LimeBlackScheme=5,
   AquaBlackScheme=6
};

input eColorScheme ColorScheme=DefaultScheme;

Dieser Code wird zum Fenster der Eigenschaften des Indikators die auftauchende Liste für die Auswahl des Farbenschemas hinzufügen. Im Anfang der Funktion OnInit() werden wir eine Zeile hinzufügen:

ClrScheme.SetScheme(ColorScheme);

Jetzt wurde der Indikator iUniOscGUI vollständig beendet, und das graphische Interface kann verschiedene Farbe haben (die Abb. 6).

 
in Abb. 6. Die verschiedenen Farbenschemen des graphischen Interfaces des Indikators iUniOscGUI 

Fazit

Der bekommende Indikator hat sich sehr nützlich nicht nur bei der Gegenüberstellung der verschiedenen Indikatoren gezeigt, sondern auch bei der Beobachtung des Einflusses der äußerlichen Parameter auf die Aussicht der Indikatoren. Der Indikator ändert seine Art praktisch sofort, wenn die Parameter sich ändern. Unter Verwendung des Eigenschaften-Fensters ist ein solcher Effekt nicht erreichbar, und es ist unmöglich, solche Eindrücke über den Einfluss der Parameter auf die Aussicht des Indikators zu bekommen.

Anwendungsdateien

  • UniOscDefines.mqh — Die Datei mit der Aufzählung der Typen der Oszillatoren.
  • CUniOsc.mqh — die Klassen des universellen Oszillators.
  • iUniOsc.mq5 — Der universell Oszillator ohne graphisches Interface.
  • UniOscGUI.mqh — Die Klassen für die Erstellung des graphischen Interfaces des Oszillators. 
  • iUniOscGUI.mq5 — Der universell Oszillator mit dem graphischen Interface. 
  • IncGUI_v4.mqh — Die Bibliothek für die Arbeit mit den graphischen Objekten und die Erstellung des graphischen Interfaces. Früher gab es die Verwirrung mit der Version der Bibliothek. Es existierten zwei Dateien der Version 3 mit den identischen Namen: im Artikel und in CodeBase (mit der erneuerten Klasse der Erstellung der Tabellen CTable). In der Datei IncGUI_v4 wurden nicht nur die Korrekturen durchgeführt, aber es wurde noch die Klasse der Erstellung der Tabellen um eine neue (aus CodeBase) ersetzt.