
Universeller Kanal mit grafischem Interface
Inhalt
- Einleitung
- Typen der zentralen Linie
- Typen der Grenzen
- Klassen für die Berechnung der Breite und das Zeichnen des Kanals
- Erstellung des Indikators des universellen Kanals
- Erstellung der Klassen des grafischen Interfaces
- Klassen der Controls (Steuerelemente) der zentralen Linie
- Klassen der Controls für die Berechnung der Breite
- Klasse der Form
- Verknüpfung des Indikators und des grafischen Interfaces
- Fazit
- Anhang
Einleitung
Es wurde bereits ein Artikel über die Erstellung eines universellen Oszillators mit grafischem Interface verfasst. Als Ergebnis bekamen wir einen interessanten und nützlichen Indikator, der die Chartanalyse wesentlich erleichtert und beschleunigt. Neben Oszillatoren gibt es auch weitere Indikatortypen für die technische Analyse, die nicht weniger interessant als Oszillatoren sind. Dazu gehören Trendindikatoren, Volatilitätsindikatoren, Volumenindikatoren und andere, die in verschiedene Kategorien unterteilt werden können. In diesem Artikel wird die Erstellung eines universellen Kanal-Indikators betrachtet.
Der Artikel über den universellen Oszillator ist relativ kompliziert und eher für erfahrene Programmierer als für Anfänger geeignet. Da das Thema dieses Artikels dem Thema des universellen Oszillators eng verbunden ist, wird der universelle Kanal auf Basis des universellen Oszillators erstellt. Auf diese Weise können auch angehende Programmierer ihre universellen Indikatoren entwickeln, ohne bei der Erstellung universeller Indikatoren mit grafischem Interface ins Detail gehen zu müssen.
Trotz vieler Ähnlichkeiten mit dem universellen Oszillator, gibt es auch prinzipielle Unterschiede. Alle Channel Indikatoren stellen drei Linien dar: zentrale, obere und untere Linie. Die zentrale Linie ist nach dem Prinzip des Zeichnens dem gleitenden Durchschnitt identisch, und in den meisten Fällen wird für das Zeichnen eines Kanals gerade der gleitende Durchschnitt verwendet. Die obere und untere Linien sind von der zentralen Linie gleich weit entfernt. Dieser Abstand kann in Punkten oder in Prozent vom Preis (Envelopes Indikator) definiert werden, es können der Wert der Standardabweichung (Bollinger Bands) oder der Wert des ATR Indikators (Keltner Kanal) verwendet werden. Der Kanal-Indikator wird unter Verwendung von zwei separaten Blöcken erstellt:
- Block für die Berechnung der zentralen Linie
- Block für das Festlegen der Breite des Kanals (oder Zeichnen der Grenzen)
Es gibt Kanäle anderen Typs, zum Beispiel, Donchian Channel (Preiskanal). Das Zeichnen des Kanals beginnt mit dem Zeichnen der Randlinien (Preisbereich), dann wird der Wert der zentralen Linie berechnet (in der Mitte des Bereichs). Aber auch dieser Kanal kann nach dem oben beschriebenen System gezeichnet werden: zuerst wird die zentrale Linie als die Mitte des Preisbereichs gezeichnet, und danach werden die Grenzen gezeichnet. Natürlich werden hier mehr Berechnungen als gewöhnlich beim Zeichnen eines Preiskanals benötigt. Da der Hauptzweck des Artikels ist, einen universellen Indikator zu erstellen, kann man einige Ausnahmen zulassen, denn dieser Ansatz wird die Anzahl der möglichen Kombinationen der zentralen Linie und der Grenzen erhöhen. Zum Beispiel kann man einen Indikator mit der zentralen Linie wie beim Preiskanal erhalten, deren Grenzen aber mit dem Abstand der Standardabweichung liegen wie bei Bollinger Bands usw.
Typen der zentralen Linie
Für die zentrale Linie werden verschiedene gleitende Durchschnitte verwendet. Definieren wir ihre Typen und die Anzahl ihrer Parameter. Alle Varianten der zentralen Linie, die im Indikator verwendet werden, sind in der Tabelle 1 angeführt.
Tabelle 1. Typen der zentralen Linie
Standardfunktion |
Name | Parameter |
---|---|---|
iAMA | Adaptive Moving Average | 1. int ama_period — AMA Periode 2. int fast_ma_period — Periode des schnellen MAs 3. int slow_ma_period — Periode des langsamen MAs 4. int ama_shift — horizontale Verschiebung des Indikators 5. ENUM_APPLIED_PRICE applied_price — Preistyp oder Handle |
iDEMA | Double Exponential Moving Average | 1. int ma_period — Mittelungsperiode 2. int ma_shift — horizontale Verschiebung des Indikators 3. ENUM_APPLIED_PRICE applied_price — Preistyp |
iFrAMA | Fractal Adaptive Moving Average | 1. int ma_period — Mittelungsperiode 2. int ma_shift — horizontale Verschiebung des Indikators 3. ENUM_APPLIED_PRICE applied_price — Preistyp |
iMA | Moving Average | 1. int ma_period — Mittelungsperiode 2. int ma_shift — horizontale Verschiebung des Indikators 3. ENUM_MA_METHOD ma_method — Typ der Glättung 4. ENUM_APPLIED_PRICE applied_price — Preistyp |
iTEMA | Triple Exponential Moving Average | 1. int ma_period — Mittelungsperiode 2. int ma_shift — horizontale Verschiebung des Indikators 3. ENUM_APPLIED_PRICE applied_price — Preistyp |
iVIDyA | Variable Index Dynamic Average | 1. int cmo_period — Chande Momentum Periode 2. int ema_period — Periode des Glättungsfaktors 3. int ma_shift — horizontale Verschiebung des Indikators 4. ENUM_APPLIED_PRICE applied_price — Preistyp |
- | zentrale Linie des Preiskanals | 1. int period |
Basierend auf der Analyse der Spalte "Parameter" aus der Tabelle 1, erhalten wir den minimalen Set von Parametern(Tabelle 2).
Tabelle 2. Universeller Set von Parametern für die Berechnung der zentralen Linie des Kanals
Typ | Name |
---|---|
int | period1 |
int | period2 |
int | period3 |
int | shift |
ENUM_MA_METHOD | ma_method |
ENUM_APPLIED_PRICE | price |
Typen der Grenzen
Legen wir die Varianten der Berechnung der Kanalgrenzen fest (Tabelle 3).
Tabelle 3. Varianten der Berechnung der Breite des Kanals
Standardfunktion |
Name | Parameter |
---|---|---|
iATR | Average True Range | 1. int ma_period — Mittelungsperiode |
iStdDev | Standard Deviation | 1. int ma_period — Mittelungsperiode 2. int ma_shift — horizontale Verschiebung des Indikators 3. ENUM_MA_METHOD — Glättungstyp 4. ENUM_APPLIED_PRICE applied_price — Preistyp |
- | in Punkten | int width — Breite in Punkten |
- | in Prozent (wie in Envelopes) | double width — Breite in Prozent vom Preis |
- | wie beim Preiskanal | double width — Skalierungsfaktor hinsichtlich der aktuellen Breite des Preiskanals |
Basierend auf der Spalte "Parameter" aus der Tabelle 3 erhalten wir den notwendigen Set von Parametern (Tabelle 4).
Tabelle 4. Universeller Set von Parametern für die Berechnung der Breite des Kanals
Typ | Name |
---|---|
int | period |
int | shift |
ENUM_MA_METHOD | ma_method |
ENUM_APPLIED_PRICE | price |
double | width |
Bei der Berechnung in Punkten wird eine Variable vom Typ int benötigt, sie ist aber nicht in der Tabelle 4 vorhanden, denn statt der Variablen kann man eine Variable vom Typ double verwenden. Auf diese Weise wird die Anzahl der Variablen im Fenster der Eigenschaften reduziert.
Die Parameter der Berechnung der Grenzen werden die Präfixe "w_" im Fenster der Eigenschaften des Indikators haben.
Klassen der zentralen Linie
Die Basisklasse der zentralen Linie ist nach dem Prinzip und dem Set von Methoden der CUniOsc Klasse aus dem Artikel Universeller Oszillator mit grafischen Interface identisch, nehmen wir diese Klasse als Grundlage und modifizieren wir sie ein bisschen.
Im Ordner MQL5/Include erstellen wir den Ordner "UniChannel", kopieren wir die Datei CUniOsc.mqh (aus dem Ordner Include/UniOsc) in diesen Ordner und benennen wir sie in CUniChannel.mqh um. Lassen wir die Basisklasse (COscUni), die abgeleitete Klasse Calculate1 (absoluter Pfad COscUni_Calculate1) und die von ihr abgeleitete Klasse COscUni_ATR in der Datei, alle anderen Klassen werden gelöscht.
Benennen wir die Klassen um: ersetzen wir das Fragment "COscUni" durch "CChannelUni". Dafür passt eine Funktion des Editors sehr gut (Hauptmenü - Bearbeiten - Suchen und ersetzen - Ersetzen), aber bitte nicht den Button "Alles ersetzen" verwenden, sondern einzeln ersetzen, um den Prozess zu kontrollieren und um sicher zu sein, dass das Fragment nur an den richtigen Stellen ersetzt wurde.
Die Klasse der zentralen Linie zeichnet immer eine durchgehende Linie, deswegen braucht man viele Methoden der Basisklasse nicht. Nach dem Löschen unnötiger Methoden bleibt die Basisklasse wie folgt:
protected:
int m_handle; // Handle des Indikators
string m_name; // Name des Indikators
string m_label1; // Name des Puffers 1
string m_help; // kurze Hilfe zu den Parametern des Indikators
double m_width; // Breite des Kanals
public:
// Konstruktor
void CChannelUniWidth(){
m_handle=INVALID_HANDLE;
}
// Destruktor
void ~CChannelUniWidth(){
if(m_handle!=INVALID_HANDLE){
IndicatorRelease(m_handle);
}
}
// Hauptmethode, die aus der OnCalculate() Funktion des Indikators aufgerufen wird
virtual int Calculate( const int rates_total,
const int prev_calculated,
double & bufferCentral[],
double & bufferUpper[],
double & bufferLower[],
){
return(rates_total);
}
// Handle des geladenen Indikators erhalten
int Handle(){
return(m_handle);
}
// Methode der Überprüfung des Handles, um festzustellen, ob der Indikator geladen wurde
bool CheckHandle(){
return(m_handle!=INVALID_HANDLE);
}
// den Namen des Indikators erhalten
string Name(){
return(m_name);
}
// Text für Puffer erhalten
string Label1(){
return(m_label1);
}
// Tooltip mit den Parametern erhalten
string Help(){
return(m_help);
}
};
Aus der Calculate Klasse kann man alles löschen, was den zweiten Puffer betrifft, so bleibt in der Klasse nur die Calculate Methode:
public:
// Hauptmethode, die aus der OnCalculate() Funktion des Indikators aufgerufen wird
// die ersten zwei Parameter sind gleich den ersten zwei Parametern
// der OnCalculate() Funktion
// dritter Parameter - Indikatorpuffer für die zentrale Linie
int Calculate( const int rates_total,
const int prev_calculated,
double & buffer0[]
){
// Anzahl der zu kopierenden Elementen festlegen
int cnt;
if(prev_calculated==0){
cnt=rates_total;
}
else{
cnt=rates_total-prev_calculated+1;
}
// Kopieren von Daten in den Puffer
if(CopyBuffer(m_handle,0,0,cnt,buffer0)<=0){
return(0);
}
return(rates_total);
}
};
Schreiben wir eine abgeleitete Klasse, die den iMA Indikator verwendet. Die CChannelUni_ATR Klasse in der Datei benennen wir in CChannelUni_MA um, ersetzen wir den Indikator, der in der Klasse aufgerufen wird, und löschen wir alles Unnötige. Als Ergebnis bekommen wir die folgende Klasse:
public:
// Konstruktor
// die ersten zwei Parameter sind gleich für alle abgeleiteten Klassen
// darauf folgen die Parameter des zu ladenden Indikators
void CChannelUni_MA( bool use_default,
bool keep_previous,
int & ma_period,
int & ma_shift,
long & ma_method,
long & ma_price){
if(use_default){ // Verwendung von Standardwerten ausgewählt
if(keep_previous){
// die früher verwendeten Parameter nicht ändern
if(ma_period==-1)ma_period=14;
if(ma_shift==-1)ma_shift=0;
if(ma_method==-1)ma_method=MODE_SMA;
if(ma_price==-1)ma_price=PRICE_CLOSE;
}
else{
ma_period=14;
ma_shift=0;
ma_method=MODE_SMA;
ma_price=PRICE_CLOSE;
}
}
// Indikator laden
m_handle=iMA(Symbol(),Period(),ma_period,ma_shift,(ENUM_MA_METHOD)ma_method,(ENUM_APPLIED_PRICE)ma_price);
// einen String mit dem Namen des Indikators bilden
m_name=StringFormat( "iMA(%i,%i,%s,%s)",
ma_period,
ma_shift,
EnumToString((ENUM_MA_METHOD)ma_method),
EnumToString((ENUM_APPLIED_PRICE)ma_price)
);
// String für den Puffernamen
m_label1=m_name;
// Tooltip mit den Parametern
m_help=StringFormat( "ma_period - c_Period1(%i), "+
"ma_shift - c_Shift(%i), "+
"ma_method - c_Method(%s)"+
"ma_price - c_Price(%s)",
ma_period,
ma_shift,
EnumToString((ENUM_MA_METHOD)ma_method),
EnumToString((ENUM_APPLIED_PRICE)ma_price)
);
}
};
Gehen wir näher auf die Bildung der Strings in den Variablen m_name und mlabel1 ein. Der Name der Indikators (m_name Variable) wird bei den Indikatoren im Unterfenster im linken oberen Ecke des Unterfenters angezeigt. Da der Kanal auf dem Preischart angezeigt wird, bleibt sein Name unsichtbar, deswegen weisen wir der m_label Variablen den gleichen ausführlichen Namen zu wie der m_name Variablen, damit wenn sich der Mauszeiger über dem zentralen Kanal befindet, wären alle seine Parameter im Tooltip zu sehen.
Die Klassen für alle Standardindikatoren werden genauso wie die Klasse für den iMA Indikator erstellt. Eine Ausnahme bildet der Preiskanal. Da der Preiskanal nicht zu den Standardindikatoren des Terminals gehört, muss er berechnet werden. Hier kann es zwei Varianten geben:
- Eine abgeleitete Klasse vom Typ Calculate erstellen und alle Berechnungen in dieser Klasse durchführen
- Einen zusätzlichen Indikator schreiben und ihn mithilfe der iCustom Funktion aufrufen
Im Anhang finden Sie die Datei CUniChannel.mqh mit abgeleiteten Klassen für alle anderen Indikatoren und den iPriceChannel Indikator. Die Daten der zentralen Linie des iPriceChannel Indikators befinden sich im Puffer 0. Wenn jemand die Klasse für einen anderen Indikator modifizieren wird, in welchem die benötigten Daten nicht im Puffer 0 sind, muss man noch eine zusätzliche abgeleitete Klasse Calculate erstellen oder eine Variable für den Pufferindex in der Basisklasse erstellen und diese auf den benötigten Wert im Kostruktor der abgeleiteten Klasse setzen.
Klassen für die Berechnung der Breite und das Zeichnen des Kanals
Nehmen wir die CUniChannel Klasse wieder als Grundlage für die Basisklasse. In die Methode der Calculate Klasse wird der Puffer des Indikators mit den Werten der zentralen Linie übergeben, zwei Puffer für die Kanalgrenzen, die mit den in der Methode berechneten Werten ausgefüllt werden. Im Gegensatz zu CUniChannel gibt es für jede Berechnungsvariante der Grenzen eigene abgeleitete Calculate Klassen, die die Indikatoren laden und in welchen die Namen der Indikatoren und Puffern gebildet werden. Die Basisklasse muss ein bisschen modifiziert werden: es muss die Variable der Breite des Kanals hinzugefügt werden; der Wert der Variablen wird durch den Konstruktor der angeleiteten Klasse gesetzt.
Speichern wir die Datei CUniChannel.mqh unter dem Namen CUniChannelWidth.mqh und nehmen wir einige Änderungen vor. Zuerst löschen wir alle abgeleiteten Klassen und lassen wir nur die Basisklasse und die Calculate Klasse. Benennen wir die Klasse CChannelUni in CChannelUniWidth um (nicht vergessen den Konstruktor, Destruktor und den Namen der Basisklasse bei der abgeleiteten Klasse, die auch geändert werden müssen). Wir erhalten die folgende Klasse:
protected:
int m_handle; // Handle des Indikators
string m_name; // Name des Indikators
string m_label1; // Name des Puffers 1
string m_help; // kurze Hilfe zu den Parametern des Indikators
double m_width; // Breite des Kanals
public:
// Konstruktor
void CChannelUniWidth(){
m_handle=INVALID_HANDLE;
}
// Destruktor
void ~CChannelUniWidth(){
if(m_handle!=INVALID_HANDLE){
IndicatorRelease(m_handle);
}
}
// Hauptmethode, die aus der OnCalculate() Funktion des Indikators aufgerufen wird
virtual int Calculate( const int rates_total,
const int prev_calculated,
double & bufferCentral[],
double & bufferUpper[],
double & bufferLower[],
){
return(rates_total);
}
// Handle des geladenen Indikators erhalten
int Handle(){
return(m_handle);
}
// Methode der Überprüfung des Handles, um festzustellen, ob der Indikator geladen wurde
bool CheckHandle(){
return(m_handle!=INVALID_HANDLE);
}
// den Namen des Indikators erhalten
string Name(){
return(m_name);
}
// Text für Puffer erhalten
string Label1(){
return(m_label1);
}
// Tooltip mit den Parametern erhalten
string Help(){
return(m_help);
}
};
Die CChannelUni_Calculate Klasse benennen wir in CChannelUni_Calculate_ATR um und fügen ihr einen Konstruktor hinzu. Der Konstruktor kann der COscUni_ATR Klasse des universellen Oszillators entnommen werden, man muss ihn aber umbenennen und ihm den Parameter der Breite hinzufügen. Es werden auch einige weitere Änderungen benötigt: man muss die Bildung der Namen des Indikators und der Puffer hinzufügen. Im Endeffekt wird die Klasse für die Berechnung der Grenzen basierend auf dem ATR Indikator wie folgt aussehen:
public:
// Konstruktor
// die ersten zwei Parameter sind gleich für alle abgeleiteten Klassen
// darauf folgen die Parameter des Indikators
// der letzte Parameter - Breite des Kanals
void CChannelUni_Calculate_ATR(bool use_default,
bool keep_previous,
int & ma_period,
double & ch_width){
if(use_default){ // Verwendung von Standardwerten ausgewählt
if(keep_previous){ // die früher verwendeten Parameter nicht ändern
if(ma_period==-1)ma_period=14;
if(ch_width==-1)ch_width=2;
}
else{
ma_period=14;
ch_width=2;
}
}
// Parameter der Breite für die Verwendung in der Berechnungsmethode speichern
m_width=ch_width;
// Indikator laden
m_handle=iATR(Symbol(),Period(),ma_period);
// String mit dem Namen des Indikators erstellen
m_name=StringFormat("ATR(%i)",ma_period);
// String mit dem Namen der Puffer
m_label1=m_name;
// Tooltip mit den Parametern
m_help=StringFormat("ma_period - Period1(%i)",ma_period); // Tooltip
}
// Hauptmethode, die aus der OnCalculate() Funktion aufgerufen wird
// die ersten zwei Parameter entsprechen den ersten zwei Parametern
// der OnCalculate() Funktion
// danach werden die Indikator-Puffer übergeben
int Calculate( const int rates_total,
const int prev_calculated,
double & bufferCentral[],
double & bufferUpper[],
double & bufferLower[],
){
// den Beginn der Berechnung definieren
int start;
if(prev_calculated==0){
start=0;
}
else{
start=prev_calculated-1;
}
// Hauptschleife der Berechnung und der Ausfüllung der Puffer
for(int i=start;i<rates_total;i++){
// Daten des Indikators für den Balken erhalten
double tmp[1];
if(CopyBuffer(m_handle,0,rates_total-i-1,1,tmp)<=0){
return(0);
}
// Multiplizieren mit dem Parameter der Breite
tmp[0]*=m_width;
// Berechnung der Werte der oberen und der unteren Grenzen
bufferUpper[i]=bufferCentral[i]+tmp[0];
bufferLower[i]=bufferCentral[i]-tmp[0];
}
return(rates_total);
}
};
Bitte beachten Sie, dass der Wert des ATR Indikators innerhalb der Hauptschleife nur für einen Balken kopiert wird. Diese Variante ist viel langsamer, als eine Reihe der Werte in den Puffer zu kopieren. Bei diesem Ansatz sparen wir einen ganzen Puffer, aber der Geschwindigkeitsverlust würde nur beim manuellen Hinzufügen des Indikators auf den Chart auftreten. Aber eine Verzögerung von einigen Zehntelsekunden ist für den Nutzer nicht relevant. Am Anfang des Testens befinden sich nur wenige Balken im Strategietester, deswegen wird der Zeitverlust beim Kopieren der Daten für jeden einzelnen Balken auch nicht bemerkbar.
Einige Varianten der Berechnung der Breite benötigen keine zusätzlichen Indikatoren, insbesondere, wenn die Breite in Punkten oder wie im Envelope Indikator festgelegt wird. In diesem Fall weisen wir der m_handle Variante der Basisklasse den Wert 0 zu (unterscheidet sich vom Wert INVALID_HANDLE).
Im Anhang finden Sie die fertige Datei CUniChannelWidth.mqh mit abgeleiteten Klassen für alle Varianten der Berechnung.
Erstellung des Indikators des universellen Kanals
Nun haben wir die oben erstellten Klassen und können den Indikator des universellen Kanals erstellen, aber noch ohne grafischen Interface.
Erstellen wir einen neuen benutzerdefinierten Indikator unter dem Namen iUniChannel im Editor. Bei der Erstellung des Indikators wählen wir die folgenden Funktionen in MQL Wizard: OnCalculate(...,open,high,low,close), OnTimer, OnChartEvent sowie erstellen drei Puffer vom Typ Line.
Für die Auswahl des Typs der zentralen Linie und des Typs des Kanals müssen wir zwei Aufzählungen erstellen. Die Aufzählungen werden in der Datei UniChannelDefines.mqh sein. Erstellen wir die Aufzählungen entsprechend den Tabellen 1 und 3:
enum ECType{
UniCh_C_AMA,
UniCh_C_DEMA,
UniCh_C_FrAMA,
UniCh_C_MA,
UniCh_C_TEMA,
UniCh_C_VIDyA,
UniCh_C_PrCh
};
// Aufzählung der Typen der Grenzen
enum EWType{
UniCh_W_ATR,
UniCh_W_StdDev,
UniCh_W_Points,
UniCh_W_Percents,
UniCh_W_PrCh
};
Die Aufzählung der Typen der zentralen Linie hat den Namen ECType, die Aufzählung der Typen der Breite des Kanals hat den Namen EWType. Fügen wir dem Indikator die Datei mit den Aufzählungen und zwei früher erstellten Dateien mit Klassen hinzu:
#include <UniChannel/CUniChannel.mqh>
#include <UniChannel/CUniChannelWidth.mqh>
Deklarieren wir zwei externe Variablen für die Auswahl von Typen der zentralen Linie und der Breite des Kanals und Variablen für die Parameter entsprechend der Tabellen 2 und 4:
input ECType CentralType = UniCh_C_MA;
input int c_Period1 = 5;
input int c_Period2 = 10;
input int c_Period3 = 15;
input int c_Shift = 0;
input ENUM_MA_METHOD c_Method = MODE_SMA;
input ENUM_APPLIED_PRICE c_Price = PRICE_CLOSE;
// Parameter der Grenzen
input EWType WidthType = UniCh_W_StdDev;
input int w_Period = 20;
input int w_Shift = 0;
input ENUM_MA_METHOD w_Method = MODE_SMA;
input ENUM_APPLIED_PRICE w_Price = PRICE_CLOSE;
input double w_Width = 2.0;
Deklarieren wir zwei Variablen, die erstmal intern sein werden, aber in der Version mit dem grafischen Interface werden sie im Fenster der Eigenschaften ausgegeben:
bool KeepPrev = false;
Der Verwendungszweck dieser Variablen wurde im Artikel über den universellen Oszillator ausführlich beschrieben: für die UseDefault Variable wird der Modus aktiviert, in welchem jeder neu ausgewählte Indikator mit Standardparametern geladen wird; für die KeepPrev Variable wird der Modus aktiviert, bei welchem die Werte der Parameter beim Wechseln der Indikatoren gespeichert werden. In der Version des Indikators ohne grafisches Interface wird der Indikator mit den Parametern aus dem Fenster der Eigenschaften geladen, deswegen ist UseDefault gleich false. Die KeepPrev Variable wird auch auf false gesetzt, denn es gibt noch kein grafisches Interface und keinen Wechsel zwischen den Indikatoren.
Bei der Initialisierung des Indikators muss man Parameter vorbereiten. Genauso wie im universellen Oszillator machen wir die Vorbereitung der Parameter in einer separaten Funktion PrepareParameters(), aber zuerst machen wir Kopien von allen externen Variablen:
int _ma_Period1;
int _ma_Period2;
int _ma_Period3;
int _ma_Shift;
long _ma_Method;
long _ma_Price;
EWType _WidthType;
int _w_Period;
int _w_Shift;
long _w_Method;
long _w_Price;
double _w_Width;
Danach schreiben wir die Funktion der Vorbereitung der Paramater:
_CentralType=CentralType;
_WidthType=WidthType;
if(UseDefault && KeepPrev){
_c_Period1=-1;
_c_Period2=-1;
_c_Period3=-1;
_c_Shift=0;
_c_Method=-1;
_c_Price=-1;
_w_Period=-1;
_w_Shift=0;
_w_Method=-1;
_w_Price=-1;
_w_Width=-1;
}
else{
_c_Period1=c_Period1;
_c_Period2=c_Period2;
_c_Period3=c_Period3;
_c_Shift=c_Shift;
_c_Method=c_Method;
_c_Price=c_Price;
_w_Period=w_Period;
_w_Shift=w_Shift;
_w_Method=w_Method;
_w_Price=w_Price;
_w_Width=w_Width;
}
}
Bitte beachten Sie, wenn die Bedingung UseDefault && KeepPrev erfüllt ist, wird allen Variablen der Wert -1 zugewiesen, und den Shift Variablen - der Wert 0, denn die Werte dieser Variablen werden nicht aus den Objekten der Indikatoren gesetzt, sondern aus der Benutzeroberfläche (Fenster der Eigenschaften des Indikators oder des grafischen Interfaces).
Nach der Vorbereitung der Parameter kann man Objekte für die Berechnung der zentralen Linie und des Kanals erstellen. Im universellen Oszillator gab es dafür die LoadOscillator() Funktion. Hier haben wir zwei Funktionen: LoadCentral() und LoadWidth(), aber zuerst deklarieren wir die Pointer-Variablen:
CChannelUniWidth * width;
Einige Indikatoren verfügen den Parameter der horizontalen Verschiebung (shift), einige haben diesen Parameter nicht, dennoch kann jeder Indikator verschoben werden. Deswegen deklarieren wir eine zusätzliche Variable shift0 mit dem Wert 0, und diese wird den Konstruktoren der Klassen übergeben. Die Verschiebung erfolgt durch die Verschiebung der Indikator-Puffer.
Die LoadCentral() Funktion:
switch(_CentralType){ // je nach dem ausgewählten Typ wird eine Klasse erstellt
case UniCh_C_AMA:
central=new CChannelUni_AMA( UseDefault,
KeepPrev,
_c_Period1,
_c_Period2,
_c_Period3,
shift0,
_c_Price);
break;
case UniCh_C_DEMA:
central=new CChannelUni_DEMA( UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Price);
break;
case UniCh_C_FrAMA:
central=new CChannelUni_FrAMA(UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Price);
break;
case UniCh_C_MA:
central=new CChannelUni_MA( UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Method,
_c_Price);
break;
case UniCh_C_TEMA:
central=new CChannelUni_TEMA( UseDefault,
KeepPrev,
_c_Period1,
shift0,
_c_Price);
break;
case UniCh_C_VIDyA:
central=new CChannelUni_VIDyA(UseDefault,
KeepPrev,
_c_Period1,
_c_Period2,
shift0,
_c_Price);
break;
case UniCh_C_PrCh:
central=new CChannelUni_PriceChannel( UseDefault,
KeepPrev,
_c_Period1);
break;
}
}
In einer der Berechnungsvarianten der Breite (Klasse CChannelUni_Calculate_InPoints) gibt es einen Parameter in Punkten, und in der Klasse ist die Anpassung diesen Parameters entsprechend der Anzahl der Stellen nach dem Komma in den Kursen vorgesehen. Für die Anpassungsfunktion muss bei der Erstellung des Objekts der Multiplikator des Parameters dem Konstruktor der Klasse übergeben werden. Für Kurse mit zwei und vier Stellen wird der Multiplikator gleich 1 sein 1, und für drei- und fünfstellige Kurse - 10. In den externen Parametern deklarieren wir die Auto5Digits Variable vom Typ bool:
Wenn Auto5Digits gleich true ist, wird der Parameter korrigiert, wenn false, wird der Wert verwendet so wie er ist. Unter Auto5Digits deklarieren wir eine weitere Variable für den Multiplikator:
Ganz am Anfang der OnInit() Funktion berechnen wir den Wert mult:
mult=10; // multiplizieren Parameter in Punkten mit 10
}
else{
mult=1; // Parameter in Punkten bleiben unverändert
}
Nun schreiben wir die LoadWidth() Funktion:
switch(_WidthType){ //je nach dem ausgewählten Typ wird eine Klasse erstellt
case UniCh_W_ATR:
width=new CChannelUni_Calculate_ATR(UseDefault,KeepPrev,_w_Period,_w_Width);
break;
case UniCh_W_StdDev:
width=new CChannelUni_Calculate_StdDev(UseDefault,KeepPrev,_w_Period,shift0,_w_Method,_w_Price,_w_Width);
break;
case UniCh_W_Points:
width=new CChannelUni_Calculate_InPoints(UseDefault,KeepPrev,_w_Width,mult);
break;
case UniCh_W_Percents:
width=new CChannelUni_Calculate_Envelopes(UseDefault,KeepPrev,_w_Width);
break;
case UniCh_W_PrCh:
width=new CChannelUni_Calculate_PriceChannel(UseDefault,KeepPrev,_w_Period,_w_Width);
break;
}
}
Nach der Erstellung der Objekte (der zentralen Linie und der Breite) überprüfen wir, ob das erfolgreich war. Wenn die Objekte erfolgreich erstellt wurden, legen wir einen kurzen Namen des Indikators fest und geben wir den Tooltip mit den Parametern über die Print() Funktion aus:
Print("Width parameters matching:",width.Help());
Die Beschriftungen und Verschiebungen werden den Puffern in der SetStyles() Funktion gesetzt:
// Namen der Puffer
PlotIndexSetString(0,PLOT_LABEL,"Central: "+central.Label1());
PlotIndexSetString(1,PLOT_LABEL,"Upper: "+width.Label1());
PlotIndexSetString(2,PLOT_LABEL,"Lower: "+width.Label1());
// Verschiebung der Puffer
PlotIndexSetInteger(0,PLOT_SHIFT,_c_Shift);
PlotIndexSetInteger(1,PLOT_SHIFT,_w_Shift);
PlotIndexSetInteger(2,PLOT_SHIFT,_w_Shift);
}
Als Ergebnis bekommen wir die folgende OnInit() Funktion:
// Vorbereitung des Multiplikators für die Korrektur des Parameters in Punkten
if(Auto5Digits && (Digits()==3 || Digits()==5)){
mult=10;
}
else{
mult=1;
}
// Vorbereitung der Parameter
PrepareParameters();
// Laden des Indikators der zentralen Linie
LoadCentral();
// überprüfen, ob die zentrale Linie erfolgreich geladen wurde
if(!central.CheckHandle()){
Alert("Central line error "+central.Name());
return(INIT_FAILED);
}
// den Indikator für die Berechnung der Breite laden
LoadWidth();
// überprüfen, ob der Indikator der Breite erfolgreich geladen wurde
if(!width.CheckHandle()){
Alert("Width error "+width.Name());
return(INIT_FAILED);
}
// Ausgabe der Tooltips
Print("Central line parameters matching: "+central.Help());
Print("Width parameters matching: "+width.Help());
// Namen setzen
ShortName="iUniChannel";
IndicatorSetString(INDICATOR_SHORTNAME,ShortName);
// Standardteil der OnInit Funktion
SetIndexBuffer(0,Label1Buffer,INDICATOR_DATA);
SetIndexBuffer(1,Label2Buffer,INDICATOR_DATA);
SetIndexBuffer(2,Label3Buffer,INDICATOR_DATA);
// Namen und Verschiebungen der Puffer setzen
SetStyles();
return(INIT_SUCCEEDED);
}
Damit ist die Erstellung des Indikators ohne grafisches Interface abgeschlossen. Mithilfe dieses Indikators kann man testen, wie alle Klassen und Parameter funktionieren, und danach mit der Erstellung des grafischen Interfaces anfangen.
Während des Testens des Indikators wurden einige negative Besonderheiten festgestellt Einer der Nachteile besteht darin, dass die Periode der zentralen Linie und die Periode der Berechnung der Breite des Kanals getrennt gesteuert werden. Eine solche Steuerung erweitert die Möglichkeiten des Indikators, aber in einigen Fälle muss man beide Perioden mithilfe eines Parameters steuern. Erweitern wir den Indikator so, dass die Periode der Breite gleich einer der drei Perioden der zentralen Linie ist. Die Auswahl einer der vier Varianten erfolgt durch eine Aufzählung (befindet sich in der Datei UniChannelDefines.mqh):
LockTo_Off,
LockTo_Period1,
LockTo_Period2,
LockTo_Period3
};
Bei der Auswahl der LockTo_Off Variante werden die Perioden separat reguliert, und in anderen Fällen entspricht der Wert des w_Period Parameters der Periode der zentralen Linie. Deklarieren wir die Variable vom Typ ELockTo direkt nach der w_Period Variablen:
Modifizieren wir die PrepareParameters() Funktion und fügen wir den folgenden Code ganz unten hinzu:
case LockTo_Period1:
_w_Period=_c_Period1;
break;
case LockTo_Period2:
_w_Period=_c_Period2;
break;
case LockTo_Period3:
_w_Period=_c_Period3;
break;
}
Ein weiterer Nachteil besteht darin, dass Informationsmeldungen über die Übereinstimmung der Parameter in den Reiter "Experten" ausgegeben werden: auf kleinen Bildschirmen geht ein Teil des Strings über den Rand hinaus. Erweitern wir den Indikator so, dass der String in eine Spalte ausgegeben wird. Verwenden wir statt der Print Funktion die eigene PrintColl() Funktion. Dieser Funktion werden zwei Parameter übergeben: die Überschrift und der String mit dem Tooltip. In der Funktion wird der String mit dem Tooltip gesplittet und in Teilen ausgegeben:
Print(caption); // Ausgabe der Überschrift
string res[];
// Nachricht splitten
int cnt=StringSplit(message,',',res);
// Ausgabe der Nachricht in Teilen
for(int i=0;i<cnt;i++){
StringTrimLeft(res[i]);
Print(res[i]);
}
}
Dementsprechend werden in der OnInit() Funktion zwei Strings für die Ausgabe der Tooltips geändert:
PrintColl("Width parameters matching:",width.Help());
Nun ist der Indikator fertig, Name der Datei im Anhang - "iUniChanhel". Fangen wir mit der Erstellung des grafischen Interfaces an.
Erstellung der Klassen des grafischen Interfaces
Das grafische Interface wird auf Basis des grafischen Interfaces des universellen Oszillators erstellt. Kopieren wir die Datei UniOsc/UniOscGUI.mqh in den Ordner "UniChannel" und benennen wir ihn in UniChannelGUI.mqh um. Das grafische Interface des universellen Kanals wird sich vom Interface des universellen Oszillators wesentlich unterscheiden, deswegen steht uns viel Arbeit bevor.
Der grundlegende Unterschied besteht darin, dass beim universellen Kanal zwei Indikatoren (der zentralen Linie und der Grenzen) unabhängig voneinander auswählt werden müssen, deswegen muss es zwei Hauptlisten der Auswahl eines Indikatortyps geben. Der ersten Liste müssen die Controls für die Parameter der zentralen Linie folgen, danach folgt die zweite Liste und die Controls der Parameter der Grenzen. Das heißt dass die zweite Liste keine festgelegten Koordinaten hat, sie müssen berechnet werden. Außer zwei Listen für die Auswahl der Typen, müssen immer zwei Felder für die Eingabe des Wertes der Verschiebung vorhanden sein, ihre Koordinaten sind auch nicht festgelegt. Ein weiteres wichtiges Moment ist die Liste für die Auswahl einer Variante, die dem w_LockPeriod Parameter entspricht. Immer wenn das Eingabefeld für den w_Period Parameter in der Gruppe der Controls für die Parameter der Breite angezeigt werden muss, muss man eine zusätzliche Dropdown-Liste anzeigen.
Zunächst einmal nehmen wie allgemeine Änderungen in der Datei UniChannelGUI.mqh vor:
1. Der Pfad zur Datei mit den Aufzählungen:
muss durch den folgenden String ersetzt werden:
2. Fügen wir das Array mit den Werten der Aufzählung ELockTo hinzu:
3. Löschen wir die Arrays mit den Aufzählungen ENUM_APPLIED_VOLUME und ENUM_STO_PRICE.
Nun fangen wir an, die CUniOscControls Klasse zu ändern.
Klasse der Controls der zentralen Linie
1. Benennen wir die CUniOscControls Klasse in CUniChannelCentralControls um.
2. Löschen wir die Deklaration der Variablen m_volume und m_sto_price in der Klasse. Dementsprechend löschen wir alles, was mit diesen Elementen verbunden ist, aus den Methoden SetPointers(), Hide() und Events().
3. Fügen wir die Variable m_last_y hinzu, in dieser Variablen wird die Y Koordinate des letzten Steuerelements festgelegt. Fügen wir eine Methode für das Erhalten des Wertes dieser Variablen hinzu - GetLastY(). Die FormHeight() Methode wird überflüssig, deswegen löschen wir sie, stattdessen fügen wir die ControlsCount() Methode hinzu, die die Anzahl der Controls in der abgeleiteten Klasse zurückgeben wird. Diese Anzahl wird für die Berechnung der Höhe der Form benötigt.
Als Ergebnis bekommen wir die folgende Basisklasse:
protected:
CSpinInputBox * m_value1; // für die Periode 1
CSpinInputBox * m_value2; // für die Periode 2
CSpinInputBox * m_value3; // für die Periode 3
CComBox * m_price; // für den Preis
CComBox * m_method; // für die Methode
int m_last_y; // Y Position des letzten Steuerelements
public:
// Y-Position des letzten Steuerelements erhalten
int GetLastY(){
return(m_last_y);
}
// Methode für die Übergabe der Pointer auf Objekte ins Objekt
void SetPointers(CSpinInputBox & value1,
CSpinInputBox & value2,
CSpinInputBox & value3,
CComBox & price,
CComBox & method){
m_value1=GetPointer(value1);
m_value2=GetPointer(value2);
m_value3=GetPointer(value3);
m_price=GetPointer(price);
m_method=GetPointer(method);
}
// Gruppe der Controls ausblenden
void Hide(){
m_value1.Hide();
m_value2.Hide();
m_value3.Hide();
m_price.Hide();
m_method.Hide();
}
// Verarbeitung der Ereignisse
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);
if(e1!=0 || e2!=0 || e3!=0 || e4!=0 || e5!=0){
return(1);
}
return(0);
}
// Methode zur Initialisierung der Controls (für die Änderung der Beschriftungen)
virtual void InitControls(){
}
// Anzeige der Gruppe der Controls
virtual void Show(int x,int y){
}
// Anzahl der Controls in der Gruppe erhalten
virtual int ControlsCount(){
return(0);
}
};
Ändern wir die abgeleitete Klasse CUniOscControls_ATR:
1. Benennen wir sie in CUniChannelCentralControls_AMA um, das wird eine Klasse für den AMA Indikator sein.
2. Entsprechend der Spalte "Parameter" aus der Tabelle 1 initialisieren wir die Controls in der InitControls() Methode, und in der Show() Methode rufen wir die Show() Methoden aller Controls auf. Der m_last_y Variablen weisen wir den Wert des letzten Steuerelements zu.
3. Löschen wir die FormHeight() Methode, stattdessen fügen wir die ControlsCount() Methode hinzu.
Wir erhalten die folgende Klasse:
void InitControls(){
// Initialisierung der Controls
m_value1.Init("c_value1",SPIN_BOX_WIDTH,1," ama_period");
m_value2.Init("c_value2",SPIN_BOX_WIDTH,1," fast_ma_period");
m_value3.Init("c_value3",SPIN_BOX_WIDTH,1," slow_ma_period");
}
// Anzeige
void Show(int x,int y){
m_value1.Show(x,y);
y+=20;
m_value2.Show(x,y);
y+=20;
m_value3.Show(x,y);
y+=20;
m_price.Show(x,y);
m_last_y=y;
}
// Erhalten der Anzahl der Elemente in der Gruppe
int ControlsCount(){
return(4);
}
};
Die Klassen für alle anderen Indikatoren der zentralen Linie werden auf die gleiche Weise erstellt, alle alten abgeleiteten Klassen der Oszillatoren werden gelöscht.
Klassen der Controls für die Berechnung der Breite
Basierend auf der erhaltenen Klasse CUniChannelCentralControls erstellen wir eine Klasse der Controls für die Parameter der Breite des Kanals. Erstellen wir eine Kopie der Klasse CUniChannelCentralControls und benennen wir diese in CUniChannelWidthControls um. In dieser Klasse werden zwei Eingabefelder (Periode und Breite), zwei Standardaufzählungen für den Mittelungstyp und Preis sowie die Aufzählung des w_LockPeriod Parameters benötigt. Nach den Änderungen bekommen wir die folgende Klasse:
protected:
CSpinInputBox * m_value1; // für die Periode
CSpinInputBox * m_value2; // für die Breite
CComBox * m_price; // für den Preis
CComBox * m_method; // für die Methode
CComBox * m_lockto; // für den Typ des Blocks
int m_last_y; // die Y-Position des letzten Steuerelements
public:
// Y-Position des letzten Steuerelements erhalten
int GetLastY(){
return(m_last_y);
}
// Methode für die Übergabe der Pointer auf Objekte ins Objekt
void SetPointers(CSpinInputBox & value1,
CSpinInputBox & value2,
CComBox & price,
CComBox & method,
CComBox & lockto){
m_value1=GetPointer(value1);
m_value2=GetPointer(value2);
m_price=GetPointer(price);
m_method=GetPointer(method);
m_lockto=GetPointer(lockto);
}
// Gruppe der Controls ausblenden
void Hide(){
m_value1.Hide();
m_value2.Hide();
m_price.Hide();
m_method.Hide();
}
// Verarbeitung der Ereignisse
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 e4=m_price.Event(id,lparam,dparam,sparam);
int e5=m_method.Event(id,lparam,dparam,sparam);
int e6=m_lockto.Event(id,lparam,dparam,sparam);
if(e1!=0 || e2!=0 || e4!=0 || e5!=0 || e6){
return(1);
}
return(0);
}
// Methode zur Initialisierung der Controls (für die Änderung der Beschriftungen)
virtual void InitControls(){
}
// Anzeige der Gruppe der Controls
virtual void Show(int x,int y){
}
// Anzahl der Controls in der Gruppe erhalten
virtual int ControlsCount(){
return(0);
}
};
Erstellen wir abgeleitete Klassen. Der Hauptunterschied von der Klasse der zentralen Linie besteht darin, dass nach dem Eingabefeld eine Dropdownliste für den w_LockPeriod Parameter erstellt werden muss. Wir erhalten die folgende Klasse für die Berechnung der Breite nach ATR:
void InitControls(){
// Initialisieung eines Steuerelements
m_value1.Init("w_value1",SPIN_BOX_WIDTH,1," period");
}
// Anzeige der Gruppe der Controls
void Show(int x,int y){
m_value1.Show(x,y);
y+=20;
m_lockto.Show(x,y);
m_last_y=y;
}
// Anzahl der Controls in der Gruppe erhalten
int ControlsCount(){
return(2);
}
};
Die Klassen für andere Varianten der Berechnung der Breite wurden auf die gleiche Weise erstellt.
Nun befinden sich in der Datei UniChannelGUI.mqh zwei Basisklassen der Controls, viele abgeleitete Klassen der Controls und die Klasse der Form. An der letzten muss noch gearbeitet werden. Wegen der Größe der Datei, werden weitere Operationen in der Datei unpraktisch sein, deswegen speichern wir die Klassen der Controls in anderen Dateien. Erstellen wir die Datei UniChannel/CUniChannelCentralControls.mqh und übertragen wir die CUniChannelCentralControls Klasse sowie alle von ihr abgeleiteten Klassen in diese Klasse und fügen wir ihr zusätzliche Dateien hinzu:
#include <UniChannel/UniChannelDefines.mqh>
Verschieben wir die Deklaration der Konstanten FORM_WIDTH, SPIN_BOX_WIDTH und COMBO_BOX_WIDTH in die Datei UniChannelDefines.mqh. Danach kann die Datei CUniChannelCentralControls kompiliert werden, um Fehler zu beheben. Die Klasse CUniChannelWidthControls muss auch in eine separate Datei verschoben werden. Danach können wir an der Klasse der Form arbeiten.
Klasse der Form
Fügen wir der Datei UniChannelGUI.mqh die zwei gerade eben erstellten Dateien hinzu:
#include <UniChannel/CUniChannelWidthControls.mqh>
Benennen wir die CUniOscForm Klasse in CUniChannelForm um. In der public Sektion löschen wir die Pointer-Variable vom Typ CUniOscControls und deklarieren wir zwei weitere Pointer-Variablen: CUniChannelCentralControls und CUniChannelWidthControls. Im Endeffekt haben wir die folgenden Variablen in der public Sektion:
CSpinInputBox m_c_value1; // Feld für die Eingabe der Periode 1
CSpinInputBox m_c_value2; // Feld für die Eingabe der Periode 2
CSpinInputBox m_c_value3; // Feld für die Eingabe der Periode 3
CComBox m_c_price; // Auswahlliste des Preises
CComBox m_c_method; // Auswahlliste der Methode
CSpinInputBox m_c_shift; // Feld für die Eingabe der Verschiebung
CComBox m_w_cmb_main; // Auswahlliste der Grenzen
CSpinInputBox m_w_value1; // Feld der Eingabe der Periode
CSpinInputBox m_w_value2; // Feld für die Eingabe der Breite
CComBox m_w_price; // Auswahlliste des Preises
CComBox m_w_method; // Auswahlliste der Methode
CComBox m_w_lockto; // Auswahlliste des Blockierens
CSpinInputBox m_w_shift; // Feld für die Eingabe der Verschiebung
// Gruppe der Controls der zentralen Linie
CUniChannelCentralControls * m_central_controls;
// Gruppe der Controls der Grenzen
CUniChannelWidthControls * m_width_controls;
In der MainProperties() Methode ändern wir die Werte der m_Name und m_Caption Variablen, andere Variablen bleiben unverändert:
m_Name = "UniChannelForm";
m_Width = FORM_WIDTH;
m_Height = 150;
m_Type = 0;
m_Caption = "UniChannel";
m_Movable = true;
m_Resizable = true;
m_CloseButton = true;
}
In der OnInitEvent() Methode rufen wir die Init() Methoden aller Controls auf, deren Beschriftungen sich nicht ändern (Set der Controls, der dem ausgewählten Indikator entspricht) und füllen wir die Dropdown-Listen aus:
// Initialisierung der Controls, die zu keiner Gruppe gehören
m_c_cmb_main.Init("cb_c_main",COMBO_BOX_WIDTH," select central");
m_w_cmb_main.Init("cb_w_main",COMBO_BOX_WIDTH," select bands");
m_c_price.Init("c_price",COMBO_BOX_WIDTH," price");
m_c_method.Init("c_method",COMBO_BOX_WIDTH," method");
m_c_shift.Init("c_shift",COMBO_BOX_WIDTH,1," shift");
m_w_price.Init("w_price",COMBO_BOX_WIDTH," price");
m_w_method.Init("w_method",COMBO_BOX_WIDTH," method");
m_w_shift.Init("w_shift",COMBO_BOX_WIDTH,1," shift");
m_w_lockto.Init("cb_w_lockto",COMBO_BOX_WIDTH," lock period");
m_w_value2.Init("w_value2",SPIN_BOX_WIDTH,0.001," width");
// Ausfüllung der Dropdown-Listen
for(int i=0;i<ArraySize(e_price);i++){
m_c_price.AddItem(EnumToString(e_price[i]));
m_w_price.AddItem(EnumToString(e_price[i]));
}
for(int i=0;i<ArraySize(e_method);i++){
m_c_method.AddItem(EnumToString(e_method[i]));
m_w_method.AddItem(EnumToString(e_method[i]));
}
for(int i=0;i<ArraySize(e_lockto);i++){
m_w_lockto.AddItem(EnumToString(e_lockto[i]));
}
// Erlaubnis, Verschiebungen von der Tastatur einzugeben
m_c_shift.SetReadOnly(false);
m_w_shift.SetReadOnly(false);
}
In den OnShowEvent() Methoden zeigen wir alle Controls an, dabei erhalten wir nach der Anzeige der Gruppen der Elemente die Y-Koordinate und zeigen die folgenden Controls entsprechend der Koordinate an:
m_c_cmb_main.Show(aLeft+10,aTop+10); // Auswahlliste der zentralen Linie
m_central_controls.Show(aLeft+10,aTop+30); // Gruppe der Controls der Parameter der zentralen Linie
int m_y=m_central_controls.GetLastY(); // die Koordinate des letzten Steuerelements erhalten
m_c_shift.Show(aLeft+10,m_y+20); // Feld für die Eingabe des Verschiebungsparameters
m_w_cmb_main.Show(aLeft+10,m_y+40); // Auswahlliste des Kanals
m_width_controls.Show(aLeft+10,m_y+60); // Gruppe der Controls der Kanalparameter
m_y=m_width_controls.GetLastY(); // Koordinate des letzten Steuerelements
m_w_value2.Show(aLeft+10,m_y+20); // Feld für die Eingabe der Breite
m_w_shift.Show(aLeft+10,m_y+40); // Feld für die Eingabe des Verschiebungsparameters
}
In der OnHideEvent() Methode blenden wir die Controls aus:
m_c_cmb_main.Hide(); // Auswahlliste der zentralen Linie
m_central_controls.Hide(); // Gruppe der Controls der Parameter der zentralen Linie
m_c_shift.Hide(); // Feld für die Eingabe des Verschiebungsparameters
m_w_cmb_main.Hide(); // Auswahlliste des Kanals
m_width_controls.Hide(); // Gruppe der Controls der Kanalparameter
m_w_shift.Hide(); // Feld für die Eingabe des Verschiebungsparameters
m_w_lockto.Hide(); // Auswahl des Typs des Blockierens der Periode
m_width_controls.Hide(); // Feld für die Eingabe der Breite
}
Nehmen wir Änderungen in der SetValues() Methode vor. Ändern wir den Set der Parameter, in der Methode setzen wir allen Controls die Werte, die diesen Parametern entsprechen:
int c_value2,
int c_value3,
long c_method,
long c_price,
long c_shift,
int w_value1,
int w_value2,
long w_method,
long w_price,
long w_lockto,
long w_shift
){
// Feld für die Eingabe der Parameter der zentralen Linie
m_c_value1.SetValue(c_value1);
m_c_value2.SetValue(c_value2);
m_c_value3.SetValue(c_value3);
m_c_shift.SetValue(c_shift);
// Feld für die Eingabe der Parameter des Kanals
m_w_value1.SetValue(w_value1);
m_w_value2.SetValue(w_value2);
m_w_shift.SetValue(w_shift);
// Anzeige ausgewählter Typen in den Auswahllisten der Glättungsmethoden
for(int i=0;i<ArraySize(e_method);i++){
if(c_method==e_method[i]){
m_c_method.SetSelectedIndex(i);
}
if(w_method==e_method[i]){
m_w_method.SetSelectedIndex(i);
}
}
// Anzeige ausgewählter Typen in den Auswahllisten des Preistyps
for(int i=0;i<ArraySize(e_price);i++){
if(c_price==e_price[i]){
m_c_price.SetSelectedIndex(i);
}
if(w_price==e_price[i]){
m_w_price.SetSelectedIndex(i);
}
}
// Anzeige des ausgewählten Typs des Blockierens der Kanalperiode
for(int i=0;i<ArraySize(e_lockto);i++){
if(w_lockto==e_lockto[i]){
m_w_lockto.SetSelectedIndex(i);
break;
}
}
}
Statt der SetType() Methode erstellen wir zwei Methoden: SetCentralType() — für das Setzen des Typs der zentralen Linie und SetWidthType() — für das Setzen der Typgrenzen. Am Ende jeder Methode werden nach der Erstellung der Objekte den Controls die Eigenschaften gesetzt, die es ermöglichen, Werte von der Tastatur einzugeben. Setzen wir auch die minimal mögliche Werte und rufen wir die private Methode der Berechnung der Höhe der Form:
Die SetCentralType() Methode:
// wenn das Objekt bereits früher erstellt wurde, löschen wir es
if(CheckPointer(m_central_controls)==POINTER_DYNAMIC){
delete(m_central_controls);
m_central_controls=NULL;
}
switch((ECType)type){ // je nach dem ausgewählten Typ wird ein Objekt erstellt
case UniCh_C_AMA:
m_central_controls=new CUniChannelCentralControls_AMA();
break;
case UniCh_C_DEMA:
m_central_controls=new CUniChannelCentralControls_DEMA();
break;
case UniCh_C_FrAMA:
m_central_controls=new CUniChannelCentralControls_FrAMA();
break;
case UniCh_C_MA:
m_central_controls=new CUniChannelCentralControls_MA();
break;
case UniCh_C_TEMA:
m_central_controls=new CUniChannelCentralControls_TEMA();
break;
case UniCh_C_VIDyA:
m_central_controls=new CUniChannelCentralControls_VIDyA();
break;
case UniCh_C_PrCh:
m_central_controls=new CUniChannelCentralControls_PrCh();
break;
}
// Pointer auf Objekte der Controls übergeben
m_central_controls.SetPointers(m_c_value1,m_c_value2,m_c_value3,m_c_price,m_c_method);
// Initialisierung der Controls
m_central_controls.InitControls();
// Eingabe von der Tastatur erlauben
m_c_value1.SetReadOnly(false);
m_c_value2.SetReadOnly(false);
m_c_value3.SetReadOnly(false);
// minimal mögliche Werte setzen
m_c_value1.SetMinValue(1);
m_c_value2.SetMinValue(1);
m_c_value3.SetMinValue(1);
// Berechnung der Höhe der Form
this.SolveHeight();
}
SetWidthType() Methode:
// wenn das Objekt bereits früher erstellt wurde, löschen wir es
if(CheckPointer(m_width_controls)==POINTER_DYNAMIC){
delete(m_width_controls);
m_width_controls=NULL;
}
switch((EWType)type){ // je nach dem ausgewählten Typ wird ein Objekt erstellt
case UniCh_W_ATR:
m_width_controls=new CUniChannelWidthControls_ATR();
break;
case UniCh_W_StdDev:
m_width_controls=new CUniChannelWidthControls_StdDev();
break;
case UniCh_W_Points:
m_width_controls=new CUniChannelWidthControls_InPoints();
break;
case UniCh_W_Percents:
m_width_controls=new CUniChannelWidthControls_Envelopes();
break;
case UniCh_W_PrCh:
m_width_controls=new CUniChannelWidthControls_PrCh();
break;
}
// Pointer auf Objekte der Controls übergeben
m_width_controls.SetPointers(m_w_value1,m_w_value2,m_w_price,m_w_method);
// Initialisierung der Controls
m_width_controls.InitControls();
// minimal zulässige Werte setzen
m_w_value1.SetReadOnly(false);
m_w_value2.SetReadOnly(false);
// minimal zulässige Werte setzen
m_w_value1.SetMinValue(1);
m_w_value2.SetMinValue(0);
// Berechnung der Höhe der Form
this.SolveHeight();
}
Am Ende der Methoden SetCentralType() und SetWidthType() wird die Methode SolveHeight() für die Berechnung der Höhe der Form aufgerufen:
// wenn beide Objekte existieren (der zentralen Linie und der Breite)
if(CheckPointer(m_central_controls)==POINTER_DYNAMIC && CheckPointer(m_width_controls)==POINTER_DYNAMIC){
m_Height=(m_width_controls.ControlsCount()+m_central_controls.ControlsCount()+6)*20+10;
}
}
Nun können wir den Indiaktor und das grafische Interface miteinander verbinden.
Verknüpfung des Indikators und des grafischen Interfaces
Speichern wird den iUniChannel Indikator unter dem Namen iUniChannelGUI. Analog zum iUniOscGUI Indikator fügen wir dem Fenster der Eigenschaften den externen Parameter UseGUI ganz oben hinzu. Im Anschluß platzieren wir die Variablen UseDefault und KeepPrev, setzen sie standardmäßig auf true und geben sie im Fenster der Eigenschaften aus:
input bool UseDefault = true;
input bool KeepPrev = true;
Fügen wir zwei Dateien mit dem grafischen Interface hinzu (auch da, wo die Dateien mit den Klassen der Indikatoren hinzugefügt werden):
Der OnInit() Funktion fügen wir ganz unten den Code des grafischen Interfaces hinzu, aber zuerst brauchen wir die Arrays mit den Typen der zentralen Linie und der Grenzen. Fügen wir diese unterhalb der externen Parametern des Indikators hinzu:
ECType ctype[]={
UniCh_C_AMA,
UniCh_C_DEMA,
UniCh_C_FrAMA,
UniCh_C_MA,
UniCh_C_TEMA,
UniCh_C_VIDyA,
UniCh_C_PrCh
};
// Array mit den Typen der Grenzen
EWType wtype[]={
UniCh_W_ATR,
UniCh_W_StdDev,
UniCh_W_Points,
UniCh_W_Percents,
UniCh_W_PrCh
};
Hier fügen wir auch die Pointer-Variable der Klasse der Form:
Ganz am Ende der OnInit() Funktion erstellen wir das Objekt des grafischen Interfaces:
// Erstellung und Initialisieung des Objekts der Form
frm=new CUniChannelForm();
frm.Init();
// Hilfsvariablen
int ind1=0;
int ind2=0;
// Suche des ausgewählten Typs der zentralen Linie im Array der Typen der zentralen Linie
for(int i=0;i<ArraySize(ctype);i++){
frm.m_c_cmb_main.AddItem(EnumToString(ctype[i]));
if(ctype[i]==_CentralType){
ind1=i;
}
}
// Suche des ausgewählten Typs der Kanalgrenzen im Array der Typen der Grenzen
for(int i=0;i<ArraySize(wtype);i++){
frm.m_w_cmb_main.AddItem(EnumToString(wtype[i]));
if(wtype[i]==_WidthType){
ind2=i;
}
}
// Anzeige des ausgewählten Typs der zentralen Linie in der Liste
frm.m_c_cmb_main.SetSelectedIndex(ind1);
// Vorbereitung der Controls, die dem Typ entsprechen
frm.SetCentralType(_CentralType);
// Anzeige des ausgewählten Typs der Grenzen in der Liste
frm.m_w_cmb_main.SetSelectedIndex(ind2);
frm.SetWidthType(_WidthType);
// Werte setzen
frm.SetValues(
_c_Period1,
_c_Period2,
_c_Period3,
_c_Method,
_c_Price,
_c_Shift,
_w_Period,
_w_Width,
_w_Method,
_w_Price,
_w_LockPeriod,
_w_Shift
);
// Eigenschaften der Form setzen
frm.SetSubWindow(0);
frm.SetPos(10,30);
// Anzeige der Form
frm.Show();
}
Außer der Erstellung des Objekts der Form, werden die Auswahllisten der Indikatoren ausgefüllt, sie werden auf die ausgewählten Varianten gesetzt. Legen wir auch alle anderen Werte in den Controls fest. Beim Hinzufügen des Indikators auf den Chart wird die Form mit den Steuerelementen angezeigt (Abb. 1).
Abb. 1. Form mit Controls des universellen Kanals
Die Controls wurden angezeigt, nun müssen wir das Funktionieren der Buttons sichern. In der OnChartEvent() Funktion werden sechs verschiedene Ereignisse verarbeitet. Die Verarbeitung einiger von ihnen ist sehr aufwendig, deswegen erfolgt sie in separaten Funktionen:
const long &lparam,
const double &dparam,
const string &sparam)
{
// Ereignisse der Form
if(frm.Event(id,lparam,dparam,sparam)==1){
EventForm();
}
// Auswahl des Typs der zentralen Linie
if(frm.m_c_cmb_main.Event(id,lparam,dparam,sparam)==1){
EventCentralTypeChange();
}
// Auswahl des Grenzentyps
if(frm.m_w_cmb_main.Event(id,lparam,dparam,sparam)==1){
EventWidthTypeChange();
}
// Änderung der Parameter der zentralen Linie
if(frm.m_central_controls.Event(id,lparam,dparam,sparam)==1){
EventCentralParametersChange();
}
// Änderung der Parameter der Grenzen
if(frm.m_width_controls.Event(id,lparam,dparam,sparam)==1){
EventWidthParametersChange();
}
// Änderung der Verschiebungsparameter
if(frm.m_c_shift.Event(id,lparam,dparam,sparam)!=0 ||
frm.m_w_shift.Event(id,lparam,dparam,sparam)
){
EventShift();
}
}
Betrachten wir alle diese Funktionen. Die Funktion EventForm():
int win=ChartWindowFind(0,ShortName); // Definition des Unterfensters des Indikators
ChartIndicatorDelete(0,win,ShortName); // Löschen des Indikators
ChartRedraw();
}
Diese Funktion wird beim Schließen der Form durch das Anklicken des Buttons mit dem Kreuz ausgeführt, dabei erfolgt die Suche des Fensters des Indikators nach seinem kurzen Namen und das Löschen des Indikators.
Die Funktion EventCentralTypeChange():
// Erhalten eines neuen Typs in die Variable
_CentralType=ctype[frm.m_c_cmb_main.SelectedIndex()];
// Löschen des alten Objekts und die Erstellung eines neuen
delete(central);
LoadCentral(true);
// Überprüfung, ob der Indikator geladen wurde
if(!central.CheckHandle()){
Alert("Fehler beim Laden des Indikators"+central.Name());
}
// Festlegung der Verschiebungen und Namen der Puffer
SetStyles();
// Festlegung eines neuen Typs in der Liste
frm.SetCentralType(ctype[frm.m_c_cmb_main.SelectedIndex()]);
// Aktualisierung der Werte der Parameter in der Form
frm.SetValues(
_c_Period1,
_c_Period2,
_c_Period3,
_c_Method,
_c_Price,
_c_Shift,
_w_Period,
_w_Width,
_w_Method,
_w_Price,
_w_LockPeriod,
_w_Shift
);
// Aktualisierung der Form
frm.Refresh();
// Starten des Timers für die Neuberechnung des Indikators
EventSetMillisecondTimer(100);
}
In dieser Funktion wird der Typ des Indikators der zentralen Linie geändert. Zuerst wird der Typ des ausgewählten Indikators erhalten, das alte Objekt wird gelöscht, ein neues wird erstellt. Bei der Erstellung des neuen Objekts können sich einige seine Parameter ändern (wegen der UseDefault Funktion), deswegen wird die SetValues() Methode für das Setzen neuer Werte für die Controls aufgerufen, die Anzeige der Form wird aktualisiert (Refresh() Methode). Am Ende wird der Timer gestartet, um den Indikator neu zu berechnen.
Die EventWidthTypeChange() Funktion ist der EventCentralTypeChange() Funktion gleich, darauf gehen wir nicht ausführlich ein.
In den Funktionen EventCentralParametersChange() und EventWidthParametersChange() wird die Reaktion der Indikatoren auf die Änderung der Parameter des Indikators gesichert. Diese zwei Funktionen sind von der Funktionalität her identisch. Aber die Änderung der Parameter erfordert, das Blockieren der Perioden zu berücksichtigen und die Parameter entsprechend dem Blockieren zu korrigieren, deswegen haben die Funktionen ihre einmaligen Besonderheiten, gehen wir auf beide näher ein.
// Variable, die die Notwendigkeit anzeigt, den Indikator der Grenzen neu zu starten
bool dolock=false;
// Änderung des Wertes der Periode 1
if((int)frm.m_c_value1.Value()>0){
// Zuweisung der Variablen des Wertes vom Steurelement
_c_Period1=(int)frm.m_c_value1.Value();
// wenn die Periode 1 mit der Periode des Indikators der Breite verbunden ist
if(_w_LockPeriod==LockTo_Period1){
// weisen wir der Variablen mit der Periode des Indikators der Breite den Wert der Periode 1 zu
_w_Period=_c_Period1;
// zeigen wir ihn auf der Form an
frm.m_w_value1.SetValue(_w_Period);
// weisen wir darauf hin, dass der zweite Indikator neu gestartet werden muss
dolock=true;
}
}
// die Änderung des Wertes der Periode 2 ist gleich der Änderung der Periode 1
if((int)frm.m_c_value2.Value()>0){
_c_Period2=(int)frm.m_c_value2.Value();
if(_w_LockPeriod==LockTo_Period2){
_w_Period=_c_Period2;
frm.m_w_value1.SetValue(_w_Period);
dolock=true;
}
}
// die Änderung des Wertes der Periode 3 ist gleich der Änderung der Periode 1
if((int)frm.m_c_value3.Value()>0){
_c_Period3=(int)frm.m_c_value3.Value();
if(_w_LockPeriod==LockTo_Period3){
_w_Period=_c_Period3;
frm.m_w_value1.SetValue(_w_Period);
dolock=true;
}
}
// Änderung der Methode
if(frm.m_c_method.SelectedIndex()!=-1){
_c_Method=e_method[frm.m_c_method.SelectedIndex()];
}
// Änderung des Preises
if(frm.m_c_price.SelectedIndex()!=-1){
_c_Price=e_price[frm.m_c_price.SelectedIndex()];
}
// Löschen des alten Objekts und Erstellen eines neuen
delete(central);
LoadCentral(false);
if(!central.CheckHandle()){
Alert("Fehler beim Laden des Indikators"+central.Name());
}
// Löschen und Erstellen eines neuen Objektes des zweiten Indikators
if(dolock){
delete(width);
LoadWidth(false);
if(!width.CheckHandle()){
Alert("Fehler beim Laden des Indikators "+width.Name());
}
}
// Verschiebungen und Puffernamen setzen
SetStyles();
// Timer für eine neue Berechnung des Indikators starten
EventSetMillisecondTimer(100);
}
In dieser Funktion wird bei der Änderung einer der drei Perioden der Wert des Parameters des Blockierens überprüft. Im Falle des Blockierens wird der Parameter für den Indikator der Grenzen geändert und in der Form aktualisiert. Die dolock Variable wird auf true gesetzt. Am Ende wird das alte Objekt des Indikators gelöscht, es wird ein neues erstellt und wenn die dolock Variable gleich true ist, wird das alte Objekt der Grenzen gelöscht und ein neues wird erstellt. Danach wird der Timer gestartet, er berechnet die Indikatoren neu.
// Variable, die die Notwendigkeit anzeigt, den Indikator der zentralen Linie neu zu starten
bool dolock=false;
// Änderung der Periode
if((int)frm.m_w_value1.Value()>0){
// Zuweisung der Variablen des Wertes vom Steurelement
_w_Period=(int)frm.m_w_value1.Value();
// Blockieren
// der Parameter der Breite ist mir der ersten Periode der zentralen Linie verbunden
if(_w_LockPeriod==LockTo_Period1){
// der Variablen des Indikators der zentralen Linie wird ein neuer Wert zugewiesen
_c_Period1=_w_Period;
// Aktualisierung der Werte in der Form
frm.m_c_value1.SetValue(_c_Period1);
// weisen wir auf die Notwendigkeit hin, den Indikator der Breite neu zu starten
dolock=true;
}
else if(_w_LockPeriod==LockTo_Period2){ // wenn die Blockierung mit der Periode 2
_c_Period2=_w_Period;
frm.m_c_value2.SetValue(_c_Period2);
dolock=true;
}
else if(_w_LockPeriod==LockTo_Period3){ // wenn die Blockierung mit der Periode 3
_c_Period3=_w_Period;
frm.m_c_value3.SetValue(_c_Period3);
dolock=true;
}
}
// Änderung der Breite des Kanals
if((double)frm.m_w_value2.Value()>0){
_w_Width=(double)frm.m_w_value2.Value();
}
// Änderung der Methode
if(frm.m_w_method.SelectedIndex()!=-1){
_w_Method=e_method[frm.m_w_method.SelectedIndex()];
}
// Änderung des Preises
if(frm.m_w_price.SelectedIndex()!=-1){
_w_Price=e_price[frm.m_w_price.SelectedIndex()];
}
// Ereignis der Änderung des Typs des Blockieren der Perioden in der Auswahlliste
if(frm.m_w_lockto.SelectedIndex()>=0){
// der Variablen den Wert aus dem Steuerungelement zuweisen
_w_LockPeriod=e_lockto[frm.m_w_lockto.SelectedIndex()];
// wenn das Blockieren mit einer der Perioden ausgewählt wurde,
// wird ihr Wert kopiert und in der Form aktualisiert
if(_w_LockPeriod==LockTo_Period1){
_w_Period=_c_Period1;
frm.m_w_value1.SetValue(_w_Period);
}
else if(_w_LockPeriod==LockTo_Period2){
_w_Period=_c_Period2;
frm.m_w_value1.SetValue(_w_Period);
}
else if(_w_LockPeriod==LockTo_Period3){
_w_Period=_c_Period3;
frm.m_w_value1.SetValue(_w_Period);
}
}
// das alte Objekt löschen und ein neues erstellen
delete(width);
LoadWidth(false);
if(!width.CheckHandle()){
Alert("Fehler beim Laden des Indikators"+width.Name());
}
// Löschen und Erstellen eines neuen Objektes des zweiten Indikators
if(dolock){
delete(central);
LoadCentral(false);
if(!central.CheckHandle()){
Alert("Fehler beim Laden des Indikators"+central.Name());
}
}
// Verschiebungen und Puffernamen setzen
SetStyles();
// Starten des Timers, der den Indikator neu berechnet
EventSetMillisecondTimer(100);
}
In dieser Funktion wird der Typ des Blockierens überprüft, und wenn nötig, wird die entsprechende Periode des Indikators der zentralen Linie geändert. Wenn ein Ereignis aus der Liste der Auswahl des Typs des Blockierens auftritt, dann wird der Variablen der Periode des Grenzen-Indikators der Wert aus der entsprechenden Variablen des Indikators der zentralen Linie zugewiesen.
Die Verarbeitung des Ereignisses der Änderung der Verschiebungswerte ist relativ einfach:
// Erhalten der neuen Werte in der Variablen
_c_Shift=(int)frm.m_c_shift.Value();
_w_Shift=(int)frm.m_w_shift.Value();
// Festlegen neuer Stile
SetStyles();
// Aktualisierung des Charts
ChartRedraw();
}
Den Variablen werden die Werte aus den Controls zugewiesen, die SetStyles() Funktion wird aufgerufen und der Chart wird aktualisiert.
Damit ist der Indikators mit grafischem Interface quasi fertig.
Beim Testen des Indikators wurde ein Nachteil aufgedeckt. Wenn der externe Parameter UseDefault aktiviert ist und das Blockieren der Periode verwendet wird, funktioniert das Blockieren nicht. Das ist damit verbunden, dass beim Laden des zweiten Indikators (des Indikators der Breite) die Parameter in seinem Konstruktor geändert werden. Um diesen Fehler zu beheben, mussten wir einige abgeleitete Klassen der Indikatoren der Breite modifizieren. Den Konstruktoren der Klassen CChannelUni_Calculate_ATR, CChannelUni_Calculate_StdDev und CChannelUni_Calculate_PriceChannel wurde der freie Parameter locked mit dem Standardwert false hinzugefügt (wenn der Parameter der Klasse nicht übergeben wird, arbeitet alles ohne Änderungen). Beim Setzen locked=true und use_default=true ändern sich die Parameter der Periode im Konstruktor (sofern locked=true). Betrachten wir ein Fragment der CChannelUni_Calculate_ATR Klasse:
if(keep_previous){
if(ma_period==-1 && !locked)ma_period=14; // Änderung
if(ch_width==-1)ch_width=2;
}
else{
if(!locked)ma_period=14; // Änderung
ch_width=2;
}
}
Der ma_period Variablen wird der Standardwert nur dann zugewiesen, wenn die Variable locked gleich false ist. Dementsprechend wird die LoadWidth() Funktion modifiziert. Am Anfang der Funktion wird der Locked Wert berechnet:
Danach wird diese Variable den Konstruktoren der Klassen bei der Erstellung der Objekte übergeben.
Fügen wir die Möglichkeit hinzu, das Farbschema zu ändern und sichern wir das Speichern der Parameter des Indikators beim Wechsel der Timeframe, wie wir das im universellen Oszillator gemacht haben. Die Verwendung von Farbschemata wird in diesem Artikel nicht betrachtet, denn das Thema wurde bereits bei der Erstellung des universellen Oszillators erläutert. Fangen wir mit dem Speichern der Parameter an.
Erstellen wir grafische Objekte mit den Werten der Parameter in der OnDeinit() Funktion, wenn der Grund der Deinitialisierung der Wechsel des Charts ist. Erstellen wir diese grafische Objekte außerhalb der Sichtweite des Charts:
// wenn der Chart nicht gewechselt wurde, löschen wir die grafischen Objekte
if(reason!=REASON_CHARTCHANGE){
ObjectDelete(0,"_CentralType");
ObjectDelete(0,"_c_Period1");
ObjectDelete(0,"_c_Period2");
ObjectDelete(0,"_c_Period3");
ObjectDelete(0,"_c_Shift");
ObjectDelete(0,"_c_Method");
ObjectDelete(0,"_c_Price");
ObjectDelete(0,"_WidthType");
ObjectDelete(0,"_w_Period");
ObjectDelete(0,"_w_LockPeriod");
ObjectDelete(0,"_w_Shift");
ObjectDelete(0,"_w_Method");
ObjectDelete(0,"_w_Price");
ObjectDelete(0,"_w_Width");
}
else{ // beim Wechseln des Charts erstellen wir grafische Objekte mit den Werten der Parameter
SaveParameter("_CentralType",(string)_CentralType);
SaveParameter("_c_Period1",(string)_c_Period1);
SaveParameter("_c_Period2",(string)_c_Period2);
SaveParameter("_c_Period3",(string)_c_Period3);
SaveParameter("_c_Shift",(string)_c_Shift);
SaveParameter("_c_Method",(string)_c_Method);
SaveParameter("_c_Price",(string)_c_Price);
SaveParameter("_WidthType",(string)_WidthType);
SaveParameter("_w_Period",(string)_w_Period);
SaveParameter("_w_LockPeriod",(string)_w_LockPeriod);
SaveParameter("_w_Shift",(string)_w_Shift);
SaveParameter("_w_Method",(string)_w_Method);
SaveParameter("_w_Price",(string)_w_Price);
SaveParameter("_w_Width",(string)_w_Width);
}
}
// Hilfsfunktion für das Speichern eines Parameters im grafischen Objekt
void SaveParameter(string name,string value){
if(ObjectFind(0,name)==-1){
ObjectCreate(0,name,OBJ_LABEL,0,0,0);
ObjectSetInteger(0,name,OBJPROP_XDISTANCE,0);
ObjectSetInteger(0,name,OBJPROP_YDISTANCE,-30);
}
ObjectSetString(0,name,OBJPROP_TEXT,value);
}
Direkt nach dem Aufruf der PrepareParameters() Funktion rufen wir die LoadSavedParameters() Funktion in der OnInit() Funktion auf:
// wenn alle Objekte mit den Parametern vorliegen
if(ObjectFind(0,"_CentralType")==0 &&
ObjectFind(0,"_c_Period1")==0 &&
ObjectFind(0,"_c_Period2")==0 &&
ObjectFind(0,"_c_Period3")==0 &&
ObjectFind(0,"_c_Shift")==0 &&
ObjectFind(0,"_c_Method")==0 &&
ObjectFind(0,"_c_Price")==0 &&
ObjectFind(0,"_WidthType")==0 &&
ObjectFind(0,"_w_Period")==0 &&
ObjectFind(0,"_w_LockPeriod")==0 &&
ObjectFind(0,"_w_Shift")==0 &&
ObjectFind(0,"_w_Method")==0 &&
ObjectFind(0,"_w_Price")==0 &&
ObjectFind(0,"_w_Width")==0
){
// Erhalten der Werte aus den grafischen Objekten
_CentralType=(ECType)ObjectGetString(0,"_CentralType",OBJPROP_TEXT);
_c_Period1=(int)ObjectGetString(0,"_c_Period1",OBJPROP_TEXT);
_c_Period2=(int)ObjectGetString(0,"_c_Period2",OBJPROP_TEXT);
_c_Period3=(int)ObjectGetString(0,"_c_Period3",OBJPROP_TEXT);
_c_Shift=(int)ObjectGetString(0,"_c_Shift",OBJPROP_TEXT);
_c_Method=(long)ObjectGetString(0,"_c_Method",OBJPROP_TEXT);
_c_Price=(long)ObjectGetString(0,"_c_Price",OBJPROP_TEXT);
_WidthType=(EWType)ObjectGetString(0,"_WidthType",OBJPROP_TEXT);
_w_Period=(int)ObjectGetString(0,"_w_Period",OBJPROP_TEXT);
_w_LockPeriod=(long)ObjectGetString(0,"_w_LockPeriod",OBJPROP_TEXT);
_w_Shift=(int)ObjectGetString(0,"_w_Shift",OBJPROP_TEXT);
_w_Method=(long)ObjectGetString(0,"_w_Method",OBJPROP_TEXT);
_w_Price=(long)ObjectGetString(0,"_w_Price",OBJPROP_TEXT);
_w_Width=(double)ObjectGetString(0,"_w_Width",OBJPROP_TEXT);
return(true);
}
else{
return(false);
}
}
In der Funktion wird überprüft, ob diese Objekte existieren, und wenn ja, werden ihre Werte verwendet, dabei liefert die Funktion true. Wenn die Funktion true zurückgegeben hat, rufen wir die Funktionen LoadCentral() und LoadWidth() mit dem Parameter false (damit die Standardparameter nicht gesetzt werden). Fragment der OnInit() Funktion:
LoadCentral(!ChartCange);
Auf die gleiche Weise wird die Funktion LoadWidth() aufgerufen:
Damit ist die Erstellung des universellen Kanals vollendet.
Fazit
Obwohl wir viel vom fertigen Code des universellen Oszillators verwendet haben, erfordert die Erstellung des universellen Kanals viel Arbeit. Der grundlegende Unterschied vom universellen Oszillator besteht darin, dass es zwei unabhängige Blöcke gibt: den der zentralen Linie und den der Grenzen. Dies hat das Arbeitspensum quasi verdoppelt.. Der Algorithmus der Änderung der Parameter ist wegen der Funktion des Blockierens der Periode auch komplizierter geworden. Des Weiteren ist auch das Hinzufügen neuer Indikatoren schwieriger geworden, denn es gibt nun zwei Indikatoren. Darüber hinaus wurde eine neue Möglichkeit hinzugefügt, und zwar das Speichern der Parameter beim Wechsel der Timeframe. Im Endeffekt haben wir einen Indikator erhalten, der genauso hilfreich wie der universelle Oszillator ist. Dieser Indikator erweitert die Möglichkeiten des Kanals, denn nun kann man die zentrale Linie und die Methode des Zeichnens der Grenzen separat auswählen, was in einer großen Anzahl verschiedener Kombinationen resultiert. Die Erhöhung der Geschwindigkeit der Anwendung des Indikators durch das grafische Interface ermöglicht es, alle Kombinationen visuell zu erforschen.
Anhang
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2888





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.