
Anlegen eines Spektrumanalysators
Einleitung
Der hier vorliegende Beitrag möchte seine Leser mit einer möglichen Variante der Verwendung der grafischen Objekte der Programmiersprache MQL5 vertraut machen. Es wird ein Indikator analysiert, der unter Verwendung grafischer Objekte ein Bedienfeld für einen einfachen Spektrumanalysator anlegt.
Im Anhang zu diesem Beitrag befinden sich drei Dateien:
- SpecAnalyzer.mq5 – der in diesem Beitrag vorgestellte Indikator.
- SpecAnalyzer.mqh – die Include-Datei zur Einbindung von SpecAnalyzer.mq5.
- SAInpData.mq5 – der zur Organisation des Zugriffs auf externe Daten verwendete Indikator.
Zum Laden des Indikators SpecAnalyzer müssen üblicherweise alle drei Dateien in das Verzeichnis \MQL5\Indicators der Anwendungsinstanz auf dem Ausgabegerät (Terminal) kopiert werden. Anschließend müssen die Dateien SpecAnalyzer.mq5 und SAInpData.mq5 kompiliert werden. Der Indikator ist dazu gedacht, in das Hauptfenster des Diagramms geladen zu werden. Nach dem er geladen ist, verändert der Indikator die mit der Art der Darstellung verbundenen Einstellungen in diesem Fenster, und wenn er entfernt wird, werden alle grafischen Objekte gelöscht. Deshalb ist es besser den Indikator in ein separates, eigens für ihn angelegtes Fenster zu laden, um zu verhindern, dass die Darstellungsart in den bereits bestehenden Fenstern des Ausgabegerätes versehentlich geändert wird.
In Anbetracht des Umstandes, dass in dem Beitrag nicht der vollständige Programmcode des betrachteten Indikators aufgeführt wird, empfiehlt es sich, bei der Lektüre stets die Möglichkeit zu haben, sich den Code in den Dateien im Anhang anzusehen.
Der in diesem Beitrag vorgestellte Indikator erhebt keinesfalls den Anspruch, ein fertiges Programm zu sein. Er ist lediglich ein Beispiel für die Verwendung der grafischen Objekte der Programmiersprache. Interessierten Lesern schlage ich vor, wenn der Wunsch dazu besteht, den in diesem Beitrag vorgestellten Code eigenhändig zu aktualisieren und zu verbessern.
Die Koordinaten
In MQL5 werden bei der Platzierung grafischer Objekte zwei Verfahren zur Festlegung der entsprechenden Koordinaten verwendet. Für einige Objekte werden die Koordinaten in der Anzahl der Bildpunkte (Pixel) in Bezug auf einen ausgewählten Punkt des Fensters angegeben; für andere erfolgt die Einbindung anhand der Zeit und dem Kurswert des in dem jeweiligen Fenster abgebildeten Diagramms.
Um zum Beispiel Objekte wie Label oder Button in einem Diagramm zu platzieren, müssen ihre Koordinaten als Anzahl von Bildpunkten in Bezug auf eine der Ecken des Diagrammfensters angegeben werden. Bei dieser Art der Adresszuweisung behalten die Objekte ihre Position unabhängig von den aktuellen Fenstereigenschaften und dem eingestellten Maßstab für die Abbildung des Diagramms in diesem Fenster. Selbst bei einer Änderung der Fenstergröße ändern diese Objekte ihre Lage nicht und behalten ihre Anbindung aneinander bei. Bei einer Verschiebung des in dem Fenster abgebildeten Diagramms mithilfe der linken Maustaste behalten diese Objekte ihre Position in Bezug auf den gewählten Ankerpunkt im Fenster bei.
Die andere Gruppe von Objekten umfasst anstelle der Anbindung an Fensterkoordinaten die Einbindung in das dort abgebildete Diagramm. Dabei handelt es sich um Objekte wie Trend Line, Rectangle usw. Als Koordinaten werden bei der Erstellung und Platzierung dieser Objekte die Werte der Zeit und der Größe des in dem Fenster abgebildeten Kursdiagramms verwendet. Bei diesem Vorgehen zur Festlegung der Koordinaten ändern diese Objekte bei einer Änderung des Maßstabes der Wiedergabe des Diagramms oder im Fall seiner Verschiebung ihre Position sowohl in Bezug auf das Fenster als auch zueinander. Bei Erscheinen eines neuen Balkens in dem Diagramm ändern die Objekte ihre Lage ebenfalls, weil sich die Zeitskala des Diagramms dabei um die Größe des Zeitraums nach links verschiebt.
In dem Indikator SpecAnalyzer kommen bei der Anlage des Bedienfeldes für den Spektrumanalysator beide Arten grafischer Objekte gleichzeitig zum Einsatz. Damit die an das Diagramm angebundenen Objekte sich in Bezug auf das Fenster nicht verschieben, richten wir für die Wiedergabe des Diagramms entlang der senkrechten Skala das Vorgehen mit festen Werten ein und für die waagerechte Diagrammskala das dem kleinstmöglichen Wiedergabemaßstab entsprechende Verfahren. Außerdem setzen wir das feste Minimum der senkrechten Achse auf 0,0 und für die waagerechte Achse des Diagramms wählen wir das Verfahren, bei dem die Einrückung des Nullbalkens vom rechten Rand sowie das Verfahren der automatischen Transition zum rechten Rand des Diagramms fehlen. Auf diese Weise erscheint der dem Nullbalken und dem Wert 0,0 des Diagramms entsprechende Koordinatenpunkt in der unteren rechten Ecke und bei einem festen Wiedergabemaßstab kann er als Ankerpunkt zur Angabe der Koordinaten für Objekte der Art Trend Line und Rectangle verwendet werden. Wenn dabei als Ankerpunkt für grafische Objekte der Art Label oder Button ebenfalls die untere rechte Ecke des Diagramms angegeben wird, sind beide Arten der platzierten Objekte eindeutig aneinander gebunden.
Die Einrichtung aller erforderlichen Diagrammeigenschaften erfolgt in der Funktion SetOwnProperty() in der Datei SpecAnalyzer.mqh.
void GRaphChart::SetOwnProperty(void) { ChartSetInteger(m_chart_id,CHART_FOREGROUND,1); ChartSetInteger(m_chart_id,CHART_SHIFT,0); ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,0); ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,1); ChartSetInteger(m_chart_id,CHART_SCALE,0); ChartSetInteger(m_chart_id,CHART_SCALEFIX_11,1); ChartSetInteger(m_chart_id,CHART_SHOW_OHLC,0); ChartSetInteger(m_chart_id,CHART_SHOW_BID_LINE,0); ChartSetInteger(m_chart_id,CHART_SHOW_ASK_LINE,0); ChartSetInteger(m_chart_id,CHART_SHOW_LAST_LINE,0); ChartSetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP,0); ChartSetInteger(m_chart_id,CHART_SHOW_GRID,0); ChartSetInteger(m_chart_id,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); ChartSetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR,0); ChartSetInteger(m_chart_id,CHART_SHOW_TRADE_LEVELS,0); ChartSetInteger(m_chart_id,CHART_COLOR_BACKGROUND,Black); ChartSetInteger(m_chart_id,CHART_COLOR_FOREGROUND,Black); ChartSetDouble(m_chart_id,CHART_FIXED_MIN,0.0); }
Neben der Einrichtung der zur Gewährleistung der Einbindung der grafischen Objekte erforderlichen Eigenschaften werden in dieser Funktion auch die Eigenschaften eingestellt, die die Zuordnung einer Farbe sowie die Unterdrückung der Ausgabe einzelner Diagrammelemente ermöglichen.
Da sich bei der Arbeit des Indikators SpecAnalyzer die Diagrammeigenschaften ändern, muss die Speicherung der vorherigen Einstellungen beim Laden des Indikators ebenso gesichert sein wie ihre Wiederherstellung, wenn der Indikator entfernt wird. In der Standardbibliothek von MQL5 sind zu diesem Zweck die virtuellen Funktionen Save() und Load() der Klasse CChart vorgesehen. Diese Funktionen dienen zur Speicherung der Eigenschaften eines Objekte der Klasse CChart in einer Datei sowie zur Wiederherstellung dieser Eigenschaften aus der angelegten Datei. Um den Bestand der gespeicherten Einstellungen zu ändern und die Verwendung von Dateioperationen bei der Speicherung der Diagrammeigenschaften zu vermeiden, werden die virtuellen Funktionen Save() und Load() der Klasse CChart überschrieben und dabei die neue Klasse GRaphChart (s. SpecAnalyzer.mqh) angelegt.
class GRaphChart : public CChart { protected: struct ChartPropertyes { double shift_size; double fixed_max; double fixed_min; double points_per_bar; long mode; bool foreground; bool shift; bool autoscroll; long scale; bool scalefix; bool scalefix_11; bool scale_pt_per_bar; bool show_ohls; bool show_bid_line; bool show_ask_line; bool show_last_line; bool show_period_sep; bool show_grid; long show_volumes; bool show_object_descr; bool show_trade_levels; long color_background; long color_foreground; long color_grid; long color_volume; long color_chart_up; long color_chart_down; long color_chart_line; long color_candle_bull; long color_candle_bear; long color_bid; long color_ask; long color_last; long color_stop_level; string ch_comment; }; ChartPropertyes ChProp; public: GRaphChart(); ~GRaphChart(); void SetOwnProperty(); virtual void Save(); virtual void Load(); };
Die Basisklasse von GRaphChart ist die Klasse CChart der Standardbibliothek von MQL5. In der Klasse GRaphChart wird einerseits das Gerüst ChartProperties definiert und andererseits das zur Speicherung der Diagrammeigenschaften im Systemspeicher statt wie in der Basisklasse in einer Datei gedachte Objekt ChProp angelegt. Die Funktion Save() füllt das Gerüst ChProp mit Daten entsprechend dem Wert der aktuellen Diagrammeigenschaften, während die Funktion Load() die zuvor gespeicherten Diagrammeigenschaften aus ihr wiederherstellt.
Die Funktion Save() wird im Konstruktor der Klasse GRaphChart aufgerufen und die Funktion Load() in ihrem Destruktor. Deshalb erfolgen das Speichern und die Wiederherstellung des vorhergehenden Zustands des Diagramms beim Anlegen bzw. Löschen eines Objektes der Klasse GRaphChart automatisch. Im Konstruktor der Klasse wird auch die oben erwähnte Funktion SetOwnProperty() aufgerufen.
//---------------------------------------------------- Constructor GRaphChart -- GRaphChart::GRaphChart() { m_chart_id=ChartID(); Save(); // Keep a original chart settings SetOwnProperty(); // Set own chart settings } //----------------------------------------------------- Destructor GRaphChart -- GRaphChart::~GRaphChart() { Load(); // Restore a previous chart settings m_chart_id=-1; }
Wir führen die Verwendung der Klasse GRaphChart an einem einfachen Beispiel vor. Dazu legen wir in dem Verarbeitungsprogramm MetaEditor einen neuen benutzerdefinierten Indikator unter der Bezeichnung Test an. In den Code dieses Indikators fügen wir die Kopfzeilendatei SpecAnalyzer.mqh ein und legen ein Objekt der Klasse GRaphChart an, wobei wir ihm zwei Zeilen hinzufügen.
//+------------------------------------------------------------------+ //| Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include "SpecAnalyzer.mqh" // <----- Including header file GRaphChart MainChart; // <----- Creating object of the class GRaphChart //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
Zur erfolgreichen Zusammenstellung des vorgeschlagenen Codes muss die Datei SpecAnalyzer.mqh in dem Verzeichnis \MQL5\Indicators der Anwendungsinstanz auf dem Ausgabegerät abgelegt werden.
Wenn auf dem Ausgabegerät ein neues Diagramm angelegt und unser Testmuster in sein Hauptfenster geladen wird, ändern sich die Eigenschaften des Diagramms, und wir sehen lediglich ein leeres, für die Wiedergabe grafischer Objekte vorbereitetes Fenster. Bei der Entfernung unseres Testindikators von dem Diagramm wird die ursprüngliche Diagrammart der Erfassung jeder Kursänderung wiederhergestellt.
Kommen wir jetzt zurück zu dem Indikator SpecAnalyzer. Am Anfang des Indikatorcodes (siehe die Datei SpecAnalyzer.mq5), erfolgt die Erstellung des Objekts MainChart der Klasse GRaphChart, was dazu führt, dass die Diagrammeigenschaften beim Laden des Indikators gespeichert werden.
button. button. button.
GRaphChart MainChart; // Create MainChart object
button. button. button.
Beim Entfernen des Indikators wird das Objekt MainChart automatisch vernichtet, wobei die ursprünglichen Eigenschaften des Diagramms in dem Destruktor seiner Klasse wiederhergestellt werden.
Das Bedienfeld
Das Aussehen des Bedienfeldes in dem Indikator SpecAnalyzer wird vollständig von den im Fenster platzierten grafischen Objekten bestimmt. Die Klasse AllGrObject vereint alle Funktionen, die zu ihrer Erstellung und für die Interaktion mit ihnen erforderlich sind, siehe die Datei SpecAnalyzer.mqh.
class AllGrObject : public CChart { protected: long m_chart_id; // chart identifier public: AllGrObject(); ~AllGrObject(); void AddLabel(string name,int fsize,string font, color col,int x,int y,string text=""); void AddButton(string name,int xsize,int ysize,color bgcol, int fsize,string font,color col,int x,int y, string text="",int state=0); void AddEdit(string name,int xsize,int ysize,color bgcol,int fsize, string font,color col,int x,int y,string text=""); void AddTrendLine(string name,color col,int style=0,int width=1); void AddArrowline(string name,color col,int style=0,int width=1); void AddRectangle(string name,color col,int style=0,int width=1); void MoveGrObject(string name,int x1,int y1,int x2,int y2); void SetButtonProp(string name,bool state,color col); long GetRowNumber(string name); void LabelTxt(string name,string str); };
Die Funktionen der Klasse, deren Bezeichnung mit Add beginnt, dienen zur Erstellung grafischer Objekte. AddButton() zum Beispiel erzeugt eine Objekt der Art Button.
Im Programm werden allen grafischen Objekten Koordinaten in der Form ihrer in Bildpunkten (Pixeln) ausgedrückten Entfernung von der unteren rechten Ecke des Diagramms zugeordnet. Bei den Objekten Trend Line, Arrowed Line und Rectangle müssen diese Koordinaten in Zeit- und Kurswerte umgewandelt werden. Diese Umwandlung erfolgt vor der Zuordnung der Koordinaten zu dem betreffenden Objekt in der Funktion MoveGrObject(). Einem Bildpunkt in der Waagerechten entspricht ein Balken, und einem Bildpunkt in der Senkrechten ein Punkt.
void AllGrObject::MoveGrObject(string name,int x1,int y1,int x2,int y2) { datetime t1[1],t2[1]; long typ; typ=ObjectGetInteger(m_chart_id,name,OBJPROP_TYPE); if(typ==OBJ_TREND||typ==OBJ_ARROWED_LINE||typ==OBJ_RECTANGLE) { CopyTime(_Symbol,_Period,x1,1,t1); CopyTime(_Symbol,_Period,x2,1,t2); ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,0,t1[0]); ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,0,_Point*y1); ObjectSetInteger(m_chart_id,name,OBJPROP_TIME,1,t2[0]); ObjectSetDouble(m_chart_id,name,OBJPROP_PRICE,1,_Point*y2); } }
Alle grafischen Objekte werden in dem Indikator jeweils nur einmal angelegt und zwar bei Aufruf von gr_object_create() aus der Funktion OnInit() des Indikators, siehe die Datei SpecAnalyzer.mq5. Bei allen Objekten mit Ausnahme von Trend Line, Arrowed Line und Rectangle werden die Koordinaten sofort festgelegt. Die Koordinaten für Objekt der Art Trend Line, Arrowed Line und Rectangle werden mithilfe des Aufrufs der Funktion gr_object_coordinate() festgelegt, die wiederum die oben angesprochene Funktion MoveGrObject() zur Umwandlung des Adresszuweisungsmodus verwendet.
void gr_object_coordinate() { GObj.MoveGrObject("Trdline1",48,150,48,360); GObj.MoveGrObject("Trdline2",176,150,176,360); GObj.MoveGrObject("Trdline3",304,150,304,360); GObj.MoveGrObject("Trdline4",432,150,432,360); GObj.MoveGrObject("Trdline5",42,350,560,350); GObj.MoveGrObject("Trdline6",42,300,560,300); GObj.MoveGrObject("Trdline7",42,250,560,250); GObj.MoveGrObject("Trdline8",42,200,560,200); GObj.MoveGrObject("Arrline1",560,150,28,150); GObj.MoveGrObject("Arrline2",560,150,560,370); GObj.MoveGrObject("Rect1",0,1,208,110); GObj.MoveGrObject("Rect2",208,1,416,110); GObj.MoveGrObject("Rect3",416,1,624,110); GObj.MoveGrObject("Rect4",0,1,624,400); GObj.MoveGrObject("Rect5",20,10,195,80); }
Der Aufruf der Funktion gr_object_coordinate() ist in der Funktion OnCalculate() des Indikators enthalten. Das gewährleistet die korrekte Neuberechnung der Koordinaten bei Bildung eines neuen Diagrammbalkens, da die Funktion bei jeder neuen Kursänderung aufgerufen wird.
Die Schaltflächen des Bedienfeldes verteilen sich auf drei Gruppen: Die erste aus vier auf der linken Seite angeordneten Schaltflächen bestehende Gruppe ermöglicht die Auswahl der Art der Darstellung der Ergebnisse der Bewertung des Spektrums der Eingangsdatenfolge durch den Indikator. Es werden (entsprechend der Anzahl der Schaltflächen) vier Darstellungsarten unterstützt:
- Amplitude/Line - Anzeige des Moduls der Fourier-Transformation mit einer linearen Skala entlang der y-Achse;
- Amplitude/dB - Anzeige des Moduls der Fourier-Transformation mit einer logarithmischen Skala entlang der y-Achse;
- Power/Line - Anzeige des Quadrats der Fourier-Transformation mit einer linearen Skala entlang der y-Achse;
- Power/dB - Anzeige des Quadrats der Fourier-Transformation mit einer logarithmischen Skala entlang der y-Achse.
Zur Verarbeitung der Betätigung der Schaltflächen dieser Gruppe wurde das folgende Codefragment in die Funktion OnChartEvent() des Indikators aufgenommen:
if(id==CHARTEVENT_OBJECT_CLICK) // Click on the gr. object { if(sparam=="Butt1") // Click on the button { GObj.SetButtonProp("Butt1",1,Chocolate); GObj.SetButtonProp("Butt2",0,SlateGray); GObj.SetButtonProp("Butt3",0,SlateGray); GObj.SetButtonProp("Butt4",0,SlateGray); YPowerFlag=0;YdBFlag=0; } if(sparam=="Butt2") // Click on the button { GObj.SetButtonProp("Butt1",0,SlateGray); GObj.SetButtonProp("Butt2",1,Chocolate); GObj.SetButtonProp("Butt3",0,SlateGray); GObj.SetButtonProp("Butt4",0,SlateGray); YPowerFlag=0;YdBFlag=1; } if(sparam=="Butt3") // Click on the button { GObj.SetButtonProp("Butt1",0,SlateGray); GObj.SetButtonProp("Butt2",0,SlateGray); GObj.SetButtonProp("Butt3",1,Chocolate); GObj.SetButtonProp("Butt4",0,SlateGray); YPowerFlag=1;YdBFlag=0; } if(sparam=="Butt4") // Click on the button { GObj.SetButtonProp("Butt1",0,SlateGray); GObj.SetButtonProp("Butt2",0,SlateGray); GObj.SetButtonProp("Butt3",0,SlateGray); GObj.SetButtonProp("Butt4",1,Chocolate); YPowerFlag=1;YdBFlag=1; }
Wird die Betätigung einer Schaltfläche festgestellt, werden alle übrigen Schaltflächen der betreffenden Gruppe auf „nicht betätigt“ umgestellt, um die gleichzeitige Betätigung mehrerer Schaltflächen aus derselben Gruppe zu verhindern. Dabei erhalten die Auszeichner YPowerFlag und YdBFlag zur Bestimmung der aktuellen Art der Darstellung die dieser entsprechenden Werte.
Die zweite Gruppe aus vier Schaltflächen ermöglicht die Auswahl der Quelle für die Eingangsdaten. Dabei kann es sich um mithilfe des Aufrufs des Indikators SAInpData.mq5 bezogene externe Daten oder um drei von dem Programm selbst erzeugte Testdatenfolgen handeln. Die letzte Schaltflächengruppe beinhaltet zwei Schaltflächen zum Vor- und Zurückrollen der Liste im Ausgabefeld für Informationstexte. Die Verarbeitung der Betätigung dieser Schaltflächen erfolgt ebenso in der Funktion OnChartEvent() des Indikators wie bei den Schaltflächen der ersten Gruppe; siehe die Datei SpecAnalyzer.mq5.
Wir präsentieren ein Anwendungsbeispiel für die Funktionen der Klasse AllGrObject anhand des bereits angelegten Versuchsindikators Test.mq5. Dazu ergänzen wir seinen Quellcode um einige weitere Zeilen und fügen die ebenfalls bereits erwähnten Funktionen gr_object_create() und gr_object_coordinate() aus der Datei SpecAnalyzer.mq5 in ihn ein.
//+------------------------------------------------------------------+ //| Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include "SpecAnalyzer.mqh" GRaphChart MainChart; AllGrObject GObj; // <----- Creating object of the class AllGrObject //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- gr_object_create(); // <----- creating graphical objects return(0); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime& time[], const double& open[], const double& high[], const double& low[], const double& close[], const long& tick_volume[], const long& volume[], const int& spread[]) { //--- MainChart.SetOwnProperty(); // <----- restoring current properties of the chart gr_object_coordinate(); // <----- setting coordinates for the graphical objects //--- return value of prev_calculated for next call return(rates_total); } //----------------------------------------------- Create all graphical objects -- void gr_object_create() { GObj.AddLabel("Title",10,"Arial",DarkGray,256,367,"Spectrum Analyzer"); GObj.AddLabel("Label1",9,"Arial",LightSteelBlue,557,128,"0"); GObj.AddLabel("Label2",9,"Arial",LightSteelBlue,422,128,"128"); GObj.AddLabel("Label3",9,"Arial",LightSteelBlue,294,128,"256"); GObj.AddLabel("Label4",9,"Arial",LightSteelBlue,166,128,"384"); GObj.AddLabel("Label5",9,"Arial",LightSteelBlue,40,128,"512"); GObj.AddLabel("Label6",9,"Arial",LightSteelBlue,28,156,"N"); GObj.AddLabel("Label7",9,"Arial",LightSteelBlue,569,141,"0.00"); GObj.AddLabel("Label8",9,"Arial",LightSteelBlue,569,191,"0.25"); GObj.AddLabel("Label9",9,"Arial",LightSteelBlue,569,241,"0.50"); GObj.AddLabel("Label10",9,"Arial",LightSteelBlue,569,291,"0.75"); GObj.AddLabel("Label11",9,"Arial",LightSteelBlue,569,341,"1.00"); GObj.AddLabel("Label12",9,"Arial",LightSteelBlue,569,358,"U"); GObj.AddLabel("Label13",9,"Arial",DarkGray,490,86,"Y-axis Mode"); GObj.AddLabel("Label14",9,"Arial",DarkGray,285,86,"Input Data"); GObj.AddLabel("Label15",9,"Arial",DarkGray,75,86,"Level"); GObj.AddLabel("Label16",9,"Arial",DarkGray,185,86,"N"); GObj.AddLabel("Label17",8,"Courier",DarkOliveGreen,64,64); GObj.AddLabel("Label18",8,"Courier",DarkOliveGreen,64,51); GObj.AddLabel("Label19",8,"Courier",DarkOliveGreen,64,38); GObj.AddLabel("Label20",8,"Courier",DarkOliveGreen,64,25); GObj.AddLabel("Label21",8,"Courier",DarkOliveGreen,64,12); GObj.AddButton("Butt1",185,18,C'32,32,32',8,"Arial",SlateGray,612,79,"Amplitude (line)",0); GObj.AddButton("Butt2",185,18,C'32,32,32',8,"Arial",Chocolate,612,61,"Amplitude (log)",1); GObj.AddButton("Butt3",185,18,C'32,32,32',8,"Arial",SlateGray,612,43,"Power (line)",0); GObj.AddButton("Butt4",185,18,C'32,32,32',8,"Arial",SlateGray,612,25,"Power (log)",0); GObj.AddButton("Butt5",185,18,C'32,32,32',8,"Arial",SlateGray,403,79,"External Data",0); GObj.AddButton("Butt6",185,18,C'32,32,32',8,"Arial",SlateGray,403,61,"Test 1. SMA(3)",0); GObj.AddButton("Butt7",185,18,C'32,32,32',8,"Arial",Chocolate,403,43,"Test 2. SMA(32)",1); GObj.AddButton("Butt8",185,18,C'32,32,32',8,"Arial",SlateGray,403,25,"Test 3. LWMA(12)",0); GObj.AddButton("Butt9",14,34,C'32,32,32',8,"Wingdings",SlateGray,36,78,"\x0431",0); GObj.AddButton("Butt10",14,34,C'32,32,32',8,"Wingdings",SlateGray,36,44,"\x0432",0); GObj.AddEdit("Edit1",35,18,Black,9,"Arial",SlateGray,180,102); GObj.AddTrendLine("Trdline1",C'32,32,32'); GObj.AddTrendLine("Trdline2",C'32,32,32'); GObj.AddTrendLine("Trdline3",C'32,32,32'); GObj.AddTrendLine("Trdline4",C'32,32,32'); GObj.AddTrendLine("Trdline5",C'32,32,32'); GObj.AddTrendLine("Trdline6",C'32,32,32'); GObj.AddTrendLine("Trdline7",C'32,32,32'); GObj.AddTrendLine("Trdline8",C'32,32,32'); GObj.AddArrowline("Arrline1",LightSteelBlue); GObj.AddArrowline("Arrline2",LightSteelBlue); GObj.AddRectangle("Rect1",C'72,72,72'); GObj.AddRectangle("Rect2",C'72,72,72'); GObj.AddRectangle("Rect3",C'72,72,72'); GObj.AddRectangle("Rect4",DarkGray); GObj.AddRectangle("Rect5",C'72,72,72'); } //---------- Set object coordinate for Trend Line, Arroved Line and Rectangle -- void gr_object_coordinate() { GObj.MoveGrObject("Trdline1",48,150,48,360); GObj.MoveGrObject("Trdline2",176,150,176,360); GObj.MoveGrObject("Trdline3",304,150,304,360); GObj.MoveGrObject("Trdline4",432,150,432,360); GObj.MoveGrObject("Trdline5",42,350,560,350); GObj.MoveGrObject("Trdline6",42,300,560,300); GObj.MoveGrObject("Trdline7",42,250,560,250); GObj.MoveGrObject("Trdline8",42,200,560,200); GObj.MoveGrObject("Arrline1",560,150,28,150); GObj.MoveGrObject("Arrline2",560,150,560,370); GObj.MoveGrObject("Rect1",0,1,208,110); GObj.MoveGrObject("Rect2",208,1,416,110); GObj.MoveGrObject("Rect3",416,1,624,110); GObj.MoveGrObject("Rect4",0,1,624,400); GObj.MoveGrObject("Rect5",20,10,195,80); } //+------------------------------------------------------------------+
Zur Gewährung des Zugriffs auf die Funktionen der Klasse AllGrObject legen wir das Objekt GObj dieser Klasse an. In die Funktion OnInit() des Indikators fügen wir den Aufruf der Funktion gr_object_create() ein, in der alle zur Gestaltung des Aussehens und des Funktionsumfangs des erstellten Indikatorbedienfeldes benötigten grafischen Objekte angelegt werden.
Wir erweitern die Funktion OnCalculate um die Aufrufe der Funktionen MainChart.SetOwnProperty() und gr_object_coordinate() so, dass die Eigenschaften des Diagramms und die Koordinaten der in diesem platzierten grafischen Objekte bei jeder neuen Kursänderung wiederhergestellt werden. Diese Wiederherstellung ist bei der Bildung eines neuen Balkens in dem Ausgangsdiagramm, der Verschiebung eines Diagrammfeldes mit der linken Maustaste oder bei einer Änderung der Diagrammeigenschaften durch den Anwender unerlässlich. Nach der Zusammenstellung und dem laden dieses Testmusters sehen wir, wie unser Bedienfeld aussieht; s. Abb. 1.
Abb. 1. Benutzeroberfläche des Bedienfeldes
Um die Platzierung des Bedienfeldes in Relation zu dem Diagramm zu veranschaulichen, lassen wir in dem vorliegenden Beispiel die Anzeige des Diagrammmaßstabs zu, s. Abb. 2.
Abb. 2. Die Diagrammmaßstab
Der Spektrumanalysator
In dem Indikator erfolgt die Bewertung eines Spektrums in 1.024 Ablesungen der Eingangsdatenfolge. Die Spektrumanalyse wird mithilfe des Algorithmus der schnellen Fourier-Transformation (FFT) ausgeführt. Die Funktion, die den FFT-Algorithmus umsetzt, stammt aus den Artikeln auf der MQL4-Webseite www.mql4.com. Für die Berechnungen wird die FFT-Funktion der Echtzeiteingangsdatenfolge verwendet; ihr Code befindet sich in der Datei SpecAnalyzer.mqh. Alle erforderlichen Schritte in Verbindung mit der Bewertung des Spektrums sind in der Funktion fft_calc() angelegt.
void fft_calc() { int i,k; realfastfouriertransform(InpData,ArraySize(InpData),false); // FFT for(i=1;i<ArraySize(Spectrum);i++) { k=i*2; Spectrum[i]=InpData[k]*InpData[k]+InpData[k+1]*InpData[k+1]; } Spectrum[0]=0.0; // Clear constant component level }
Bei Aufruf der Funktion fft_calc() muss das Datenfeld InpData[] die zu analysierende Eingangsdatenfolge enthalten. Nach Ausführung der Funktion realfastfouriertransform() wird das Ergebnis der FFT in diesem Datenfeld abgelegt. Weiterhin wird aus dem realen und dem irrealen Teil der Spektrumbewertung für jede harmonische Schwingung das Quadrat des Absolutbetrags errechnet und in dem Datenfeld Spectrum[] abgelegt. Die Kennziffer des Elementes im Datenfeld Spectrum[] entspricht der Nummer der harmonischen Schwingung. Da der berechnete Wert der konstanten Komponente in dem Indikator nicht verwendet wird, erhält das Element Spectrum[0] des Datenfeldes stets den Wert „0“.
Je nach Wert der Variablen InputSource kann das Datenfeld InpData[] entweder mit der Testdatenfolge oder mit aus einem externen Indikator bezogenen Daten gefüllt werden. Die Zusammenstellung der Eingangsdaten erfolgt in der Funktion get_input_data().
void get_input_data() { int i; ArrayInitialize(InpData,0.0); switch(InputSource) { case 0: // External Data if(ExtHandle!=INVALID_HANDLE) CopyBuffer(ExtHandle,0,0,ArraySize(InpData),InpData); break; case 1: // Test 1. SMA(3) for(i=0;i<3;i++)InpData[i]=1; break; case 2: // Test 2. SMA(32) for(i=0;i<32;i++)InpData[i]=1; break; case 3: // Test 3. LWMA(12) for(i=0;i<12;i++)InpData[i]=12-i; break; } }
Ist der Wert von InputSource gleich Null, werden 1.024 Werte aus dem Nullpuffer des Indikators SAInpData.mq5 in das Eingangsdatenfeld InpData[] kopiert. Die für die Analyse gedachten Daten werden sowohl in diesem Indikator selbst als auch nach Abruf aus ihm in anderen Indikatoren angelegt. Zur Gewährleistung des Zugriffs auf den Indikator SAInpData.mq5 wird die Funktion OnInit() um folgende Zeile erweitert, in ihr wird der Wert der Variablen ExtHandle festgelegt.
int OnInit() { button. button. button. ExtHandle=iCustom(NULL,0,"SAInpData"); // External indicator Handle return(0); }
Der Indikator SAInpData.mq5 muss in dem Verzeichnis \MQL5\Indicators der Anwendungsinstanz auf dem Ausgabegerät abgelegt sein. Der im Anhang zu diesem Beitrag als Beispiel angeführte Indikator SAInpData.mq5 gibt eine willkürliche Datenfolge an den Spektrumanalysator weiter. Um SAInpData.mq5 zu veranlassen, eine andere Datenfolge an den Analysator weiterzugeben, muss sein Ausgangscode geändert werden.
Als Testdatenfolgen werden in der Funktion get_input_data() die Impulscharakteristiken der gleitenden Durchschnittswerte SMA(3), SMA(32) und LWMA(12) erzeugt. Berücksichtigt man, dass die Fourier-Transformation einer Impulscharakteristik eines Filters der AFC (Amplituden-Frequenz-Charakteristik) dieses Filters entspricht, können wir uns bei der Auswahl der Testdatenfolgen an der AFC dieser gleitenden Durchschnittswerte orientieren.
Zur Normalisierung der in dem Datenfeld Spectrum[] gespeicherten Ergebnisse der Spektrumbewertung und zu ihrer Aufbereitung für die Wiedergabe in der angegebenen Art der Darstellung kommt die Funktion norm_and_draw() zum Einsatz, siehe die Datei SpecAnalyzer.mq5. Je nach gewünschter Art der Darstellung ändert diese Funktion auch die Beschriftung der y-Achse.
Die Ergebnisse der Bewertung des Spektrums der Eingangsdatenfolge werden nicht nur bildlich sondern auch in Textform ausgegeben. Zur Präsentation der Ergebnisse in Textform werden fünf grafische Objekte der Art Label angelegt, sie entsprechen den fünf Textzeilen der Anzeige. Die Funktion list_levels() sorgt dafür, dass diese Zeilen mit Daten gefüllt werden.
void list_levels() { int m; string str; if(YdBFlag==0) str="%3u %.5f"; // If Y-axis mode = Line else str="%3u %6.1f dB"; // If Y-axis mode = dB m=ArraySize(ListArray)-5; if(ListPointer<1)ListPointer=1; if(ListPointer>m)ListPointer=m; GObj.LabelTxt("Label17",StringFormat(str,ListPointer,ListArray[ListPointer])); GObj.LabelTxt("Label18",StringFormat(str,ListPointer+1,ListArray[ListPointer+1])); GObj.LabelTxt("Label19",StringFormat(str,ListPointer+2,ListArray[ListPointer+2])); GObj.LabelTxt("Label20",StringFormat(str,ListPointer+3,ListArray[ListPointer+3])); GObj.LabelTxt("Label21",StringFormat(str,ListPointer+4,ListArray[ListPointer+4])); }
In diesen Zeilen werden die mithilfe der Funktion StringFormat() formatierten Werte für die (Ebenen-) Grenzen aus dem Datenfeld ListArray[] ausgegeben. Entsprechend der aktuellen Art der Darstellung wird dieses Datenfeld mit den im Hauptteil der Funktion norm_and_draw(), siehe die Datei SpecAnalyzer.mq5, enthaltenen Daten gefüllt. Die Abbildung der Daten aus dem Datenfeld ListArray[] beginnt mit der Kennziffer in dem Datenfeld, die dem in der Variablen ListPointer gespeicherten Wert entspricht. Der Wert der Variablen ListPointer und damit auch die Anfangskennziffer der auszugebenden Zeilen kann durch Anklicken der Schaltflächen auf der rechten Seite des Bedienfeldes oder durch Eingabe der betreffenden Kennziffer in das Eingabefeld geändert werden. Die Verarbeitung der mit der Betätigung dieser Schaltflächen und dem Abschluss der Bearbeitung in dem Eingabefeld verbundenen Ereignisse erfolgt in der Funktion OnChartEvent() des Indikators, siehe die Datei SpecAnalyzer.mq5.
Wie der Indikator SpecAnalyzer aussieht, zeigt folgende Abbildung:
Abb. 3. So sieht er aus der Indikator SpecAnalyzer.
Fazit
Wie bereits gesagt handelt es sich bei dem Indikator SpecAnalyzer.mq5 lediglich um den Prototypen eines vollwertigen Spektrumanalysators, und er wird in diesem Beitrag nur als Beispiel für die Verwendung grafischer Objekte verwendet. Um einen echten Indikator zu programmieren, sind neben der Generalüberholung seiner äußeren Gestaltung höchstwahrscheinlich auch die Entwicklung einer funktionaleren Benutzeroberfläche sowie natürlich die Nachbesserung des Algorithmus zur Bewertung des Spektrums erforderlich.
Das Aussehen des Indikators lässt sich erheblich verbessern, indem man bei seiner Erstellung ein grafisches Objekt vom Typ Bitmap verwendet, nachdem man in einem Bildbearbeitungsprogramm eine Abbildung der Frontansicht des Indikatorbedienfeldes angelegt hat und diese Abbildung als Hintergrundebene importiert, auf der die Steuerelemente dargestellt werden. Dieser Ansatz eignet sich auch zur Erstellung von Indikatoren mit austauschbarer Gestaltung.
Literaturhinweise
- Yukio Sato. Introduction to Signal Management (Verarbeitung von Signalen. Einführung).
- L. Rabiner, B. Gold. Theory and Application of Digital Signal Processing (Theorie und Anwendung der digitalen Signalverarbeitung).
- S.L. Marple, Jr. Digital Spectral Analysis with Applications (Digitale Spektrumanalyse und ihre Anwendungsgebiete).
Dateien
- SpecAnalyzer.mq5 – der in diesem Beitrag vorgestellte Indikator.
- SpecAnalyzer.mqh – die Include-Datei zur Einbindung von SpecAnalyzer.mq5.
- SAInpData.mq5 – der zur Organisation des Zugriffs auf externe Daten verwendete Indikator.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/185





- 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.