
3D-Modellierung in MQL5
In Bezug auf Finanzmärkte wird die 3D-Modellierung für eine dreidimensionale Darstellung von Zeitreihen verwendet. Als Zeitreihe wird ein dynamisches System bezeichnet, in welches Werte einer zufälligen Größe einer nach dem anderen eintreffen: kontinuierlich oder in gewissen Zeitabständen (Ticks, Balken, Fraktale u.a.). In diesem Artikel betrachten wir die Visualisierung einer 3D-Darstellung von Zeitreihen, solchen wie Timeseries und Indikatoren (s. Abb. 1).
Abb. 1. Beispiele für die 3D-Darstellung von Zeitreihen.
Eine dreidimensionale Darstellung unterscheidet sich von einer zweidimensionalen auf dem Bildschirm dadurch, dass sie das Zeichnen der geometrischen Projektion eines dreidimensionalen Modells auf eine Ebene mithilfe spezieller Funktionen voraussetzt. Um eine dreidimensionale Abbildung auf einer Ebene zu erhalten, werden folgende Schritte benötigt:
- Modellierung — Erstellung des dreidimensionalen mathematischen Modells einer Zeitreihe;
- Rendering (Visualisierung) — Erstellung einer Projektion entsprechend dem ausgewählten Modell;
- Anzeige der Abbildung auf dem Bildschirm.
Der Artikel hat zum Zweck, gewohnte Charts im dreidimensionalen Raum darzustellen. Da es momentan noch keine fertigen Lösungen für die 3D-Modellierung in MQL5 gibt, fangen wir mit grundlegenden Methoden an, und zwar mit 3D-Objekten und Koordinatensystemen. Es kann sein, dass viele Leser skeptisch zum Thema eingestellt sind und den Artikel nur schnell überfliegen, einige Algorithmen und Methoden können aber auch beim Lösen anderer Aufgaben, die mit der 3D-Visualisierung nicht verbunden sind, nützlich sein.
Interaktives grafisches Objekt
Wir fangen mit 3D-Objekten an. Die zahlreichen MQL5 Funktionen erlauben es, mit zweidimensionalen Objekten zu operieren und komplexe grafische Zeichnungen zu erstellen. Es ist ausreichend, nur wenige Funktionen hinzuzufügen, und die dreidimensionale Grafik wird in einem MT5 Terminal verfügbar sein.
Zunächst einmal listen wir auf, welche Voraussetzungen beim Entwerfen von Basisklassen von 3D Objekten erfüllt werden müssen.
- Benutzerfreundlichkeit.
- Hohe "Vitalität".
- Eigenständigkeit.
- Interaktivität.
Benutzerfreundlichkeit.
Für den Entwickler und Nutzer muss ein minimaler Set von Funktionen erstellt werden, der für die Lösung der meisten Aufgaben der 3D-Grafik ausreichend sein wird.
Hohe "Vitalität".
Ein 3D-Objekt muss "unkillable" sein, während das Programm läuft. Es muss vor einem zufälligen oder absichtlichen Löschen sowie vor Änderung von Basiseigenschaften geschützt sein.
Eigenständigkeit.
Das Objekt muss auch einen "Intellekt" haben und sich an die sich ändernden Bedingungen anpassen (Drehung des Koordinatensystems, Änderung der Ankerpunkte usw.). Es muss die notwendigen Informationen aus allen eingehenden Ereignissen ausfiltern und entsprechend reagieren.
Interaktivität.
Die 3D-Visualisierung bedeutet, dass man den Blickwinkel auf das dreidimensionales Modell ändern kann (Drehung des Koordinatensystems). Deswegen muss man solche Funktionen erstellen, die keine zusätzlichen Panels oder andere Tools benötigen. Im Grunde genommen sind grafische Objekte in MQL5 bereits interaktiv: man kann sie markieren, verschieben, ihre Eigenschaften ändern usw. Es reicht, diese Eigenschaft bis auf die Ebene einer kollektiven Steuerung und Zusammenwirkung zu vervollkommnen. Zum Beispiel: wir haben das Koordinatenzentrum verschoben, und alle damit verbundenen Objekte haben sich neu gezeichnet, dabei ohne Fehler.
Wenn diese Voraussetzungen erfüllt sind, wird ein 3D-Objekt zu einem interaktiven grafischen Objekt (IGO). Ein interaktives grafisches Objekt ist unbedingt mit einem grafischen Objekt der MQL5-Programmiersprache verbunden. Betrachten wir die Basisklasse CIGO für interaktive grafische Objekte.
{
protected:
bool on_event; // Flag der Verarbeitung von Ereignissen
int m_layer; // Schicht, zu welcher das IGO gehört
//---
double SetPrice(double def,int prop_modifier=0);
public:
string m_name; // Name des IG-Objekts
double m_price; // Ankerpunkt des IGO [Preis]
double m_angle; // Wert des Ankerwinkels IGO [Grad]
CIGO();
~CIGO();
//---
virtual // Methode: IGO erstellen
void Create(string name) {on_event=true;}
virtual // Methode: IGO neu zeichnen
void Redraw() {ChartRedraw();}
virtual // Methode zur Verarbeitung des Ereignisses OnChartEvent
bool OnEvent(const int id, // Identifizierer des Ereignisses
const long &lparam, // Parameter des Ereignisses vom Typ long
const double &dparam, // Parameter des Ereignisses vom Typ double
const string &sparam, // Parameter des Ereignisses vom Typ string
int iparamemr=0, // Identifizierer des Ereignisses des IGO
double dparametr=0.0);// Parameter des Ereignisses des IGO vom Typ double
};
Die Basisklasse enthält Minimum an Feldern und Methoden, die man später neu definieren und in Nachfolgerklassen ergänzen kann. Wir gehen nur auf zwei Methoden der Klasse ein: die virtuelle Methode OnEvent() zur Verarbeitung von Ereignissen OnChartEvent und Methode zum Setzen eines Ankerpunktes SetPrice(). Wir erläutern diese Methoden, denn gerade in ihnen werden die wichtigsten Grundsätze interaktiver grafischer Objekte umgesetzt.
Methode: Verarbeitung eingehender Ereignisse OnEvent.
Verarbeitet Ereignisse, die bei Operationen mit Charts vom Kundenterminal eingehen. Die Methode reagiert nur auf 4 Standardereignisse: Löschen des grafischen Objekts, Änderung der Chartgröße oder -eigenschaften, Verschiebung des grafischen Objekts und Mausklick auf dem grafischen Objekt. Gehen wir ausführlicher darauf ein.
- Löschen des grafischen Objekts. Beim Auftreten dieses Ereignisses ist das Objekt nicht mehr auf dem Chart - es wurde gelöscht. Wenn wir aber die Voraussetzung der "Vitalität" ausfüllen wollen, müssen wir es umgehend wiederherstellen, d.h. erneut erstellen, dabei mit den gleichen Eigenschaft, die es vor dem Löschen hatte. Aber beachten Sie: es wird nur das grafische Objekt und nicht die an das Objekt gebundene Instanz der CIGO Klasse gelöscht. Die Instanz der Klasse bleibt weiter bestehen. Sie beinhaltet Informationen über das gelöschte grafische Objekt, deswegen kann die Instanz es mithilfe der Methode Create() ganz einfach wiederherstellen.
- Änderung der Chartgröße oder Charteigenschaften. Es gibt sehr viele Ereignisse von diesem Typ: Auftreten eines neuen Balkens, Änderung des Maßstabs des Charts, Änderung der Timeframe usw. Es muss nur eine Reaktion darauf geben: das Objekt muss unter Berücksichtigung der geänderten Umgebung mithilfe der Methode Redraw() neu gezeichnet werden. Es ist wichtig anzumerken, dass bei der Änderung der Timeframe die Instanz der Klasse neu initialisiert wird. Sie verliert die Daten über das erstellte grafische Objekt, die in Feldern der Klasse gespeichert wurden, obwohl das grafische Objekt selbst auf dem Chart geblieben ist. Aus diesem Grund werden die Felder der Klasseninstanz anhand der Werte der Eigenschaften des grafischen Objekts wiederhergestellt. Dies erhöht die Vitalität des IGO.
- Bewegung des grafisches Objekts. Auf diesem Ereignis basiert die Interaktivität des grafischen Objekts. Beim Bewegen werden seine Ankerpunkte geändert. Vor dem Bewegen eines Objekts, muss es ausgewählt werden (mit einem Doppelklick markiert). Wenn dieses Ereignis aufgetreten ist, gibt die Methode true zurück, andernfalls — false. Diesen Wert brauchen wir, wenn wir kollektives Arbeiten interaktiver grafischer Objekte gestalten werden. Es ist zu betonen, wenn wir ein grafisches Objekt benötigen, das nicht bewegt werden darf, reicht es bei der Erstellung des Objekts die Option "Auswählen" zu verbieten.
- Mausklick auf dem grafischen Objekt. Wenn man auf einem anderen Objekt klickt, wählen wir das Objekt ab, damit es nicht durch Zufall verschoben wird. Dementsprechend kann nur ein IGO Objekt auf dem Chart markiert werden.
bool CIGO::OnEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam,
int iparamemr=0,
double dparametr=0.0)
{
bool res=false;
if(on_event) // Verarbeitung von Ereignissen erlaubt
{
// Löschen des grafischen Objekts
if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DELETE && sparam==m_name)
{
Create(m_name);
}
// Änderung der Chartgröße oder Änderung der Charteigenschaften über den Eigenschaftendialog
if((ENUM_CHART_EVENT)id==CHARTEVENT_CHART_CHANGE)
{
Redraw();
}
// Bewegung des grafischen Obejkts
if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DRAG)
if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==1 && sparam==m_name)
{
m_price=ObjectGetDouble(0,m_name,OBJPROP_PRICE);
Redraw();
res=true; // melden, dass sich der Ankerpunkt geändert hat
}
// Mausklick nicht auf diesem grafischen Objekt
if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_CLICK && sparam!=m_name)
{
ObjectSetInteger(0,m_name,OBJPROP_SELECTED,0);
ChartRedraw();
}
}
return(res);
}
Parameter:
id
[in] Eventbezeichner. Es gibt 9 Typen von Ereignissen, die mithilfe dieser Methode verarbeitet werden können.
lparam
[in] Parameter des Events vom Typ long.
dparam
[in] Parameter des Events vom Typ double.
sparam
[in] Parameter des Events vom Typ string.
iparametr
[in] Bezeichner eines benutzerdefinierten Ereignisses.
dparametr
[in] Parameter des Events vom Typ double.
Rückgabewert:
Gibt true zurück, wenn sich die Koordinaten des Ankerpunktes des Objekts geändert haben. Andernfalls — false.
Methode: setzt die Koordinate des Ankerpunkts SetPrice.
Setzt den Wert "price" im Koordinatensystem "Grafik" für das Feld m_price der Instanz der Klasse.
Ich erkläre, was passiert, wenn die Koordinate des Ankerpunktes anhand dieser Methode abgerufen wird.
- Bei der Initialisierung der Instanz beinhaltet das Feld m_price (Koordinate des Ankerpunkts) keinen Eingabewert, deswegen m_price=NULL. Die Initialisierung erfolgt entweder bei der Erstellung einer Instanz der Klasse oder bei der Änderung der Timeframe. Aber das grafische Objekt selbst kann auf dem Chart existieren. Es kann sein, dass es vom letzten Abruf des Programms oder bei der Änderung der Timeframe geblieben ist. Wir weisen dem Feld m_price den Wert der entsprechenden Eigenschaft des grafischen Objekts zu.
- Wenn kein grafisches Objekt mit dem Namen m_name existiert, ist die Koordinate des Ankerpunkts nach dem ersten Schritt immer noch nicht definiert: m_price=NULL. In diesem Fall wir das Feld m_price standardmäßig auf def gesetzt.
{
if(m_price==NULL) // wenn der Variablenwert nicht vorhanden ist
m_price=ObjectGetDouble(0,m_name,OBJPROP_PRICE,prop_modifier);
if(m_price==NULL) // wenn die Koordinaten nicht vorliegen
m_price=def; // standardmäßiger Wert
return(m_price);
}
Parameter:
def
[in] Standardmäßiger Wert der Variablen.
prop_modifier
[in] Modifier der abgerufenen Eigenschaft des grafischen Objekts.
Rückgabewert:
Koordinate des Ankerpunkts.
Nun schauen wir uns direkte Nachfolger der Basisklasse für interaktive Objekte an. In erster Linie interessieren uns 3D-Objekte für die Erstellung der notwendigen Umgebung für die 3D-Modellierung.
Klasse C_OBJ_ARROW_RIGHT_PRICE: Objekt "Rechtes Preisschild"
Direkter Nachfolger der CIGO-Basisklasse.
//| Klasse OBJ_ARROW_RIGHT_PRICE: Objekt "Rechtes Preisschild" |
//+------------------------------------------------------------------+
class C_OBJ_ARROW_RIGHT_PRICE:public CIGO
{
public:
virtual // Methode: Objekt erstellen
void Create(string name);
virtual // Methode: Objekt neu zeichnen
void Redraw();
};
//+------------------------------------------------------------------+
//| Methode: Objekt erstellen |
//+------------------------------------------------------------------+
void C_OBJ_ARROW_RIGHT_PRICE::Create(string name)
{
m_name=name;
m_price=SetPrice((ChartGetDouble(0,CHART_PRICE_MAX)+ChartGetDouble(0,CHART_PRICE_MIN))/2);
ObjectCreate(0,m_name,OBJ_ARROW_RIGHT_PRICE,0,0,0);
ObjectSetInteger(0,m_name,OBJPROP_SELECTABLE,true);
ObjectSetInteger(0,m_name,OBJPROP_WIDTH,1);
ObjectSetInteger(0,m_name,OBJPROP_COLOR,clrISO);
//---
ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
ObjectSetDouble(0,m_name,OBJPROP_PRICE,0,m_price);
//---
ChartRedraw();
on_event=true; // Verarbeitung von Events erlauben
}
//+------------------------------------------------------------------+
//| Methode: Objekt neu zeichnen |
//+------------------------------------------------------------------+
void C_OBJ_ARROW_RIGHT_PRICE::Redraw()
{
ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
ChartRedraw();
}
Diese Klasse passt am besten für die Gestaltung des Zentrums des 3D-Koordinatensystems. Die Instanz der Klasse ist an den aktuellen Balken gebunden und liegt quasi an der Z-Achse.
Klasse C_OBJ_TREND: Objekt "Trendlinie"
Direkter Nachfolger der CIGO-Basisklasse.
//| Klasse OBJ_TREND: Objekt "Trendlinie" |
//+------------------------------------------------------------------+
class C_OBJ_TREND:public CIGO
{
public:
virtual // Methode: Objekt erstellen
void Create(string name);
virtual // Methode: Objekt neu zeichnen
void Redraw();
};
//+------------------------------------------------------------------+
//| Methode: Objekt erstellen |
//+------------------------------------------------------------------+
void C_OBJ_TREND::Create(string name)
{
m_name=name;
m_price=(ChartGetDouble(0,CHART_PRICE_MAX)+ChartGetDouble(0,CHART_PRICE_MIN))/2;
ObjectCreate(0,m_name,OBJ_TREND,0,0,0);
ObjectSetInteger(0,m_name,OBJPROP_COLOR,clrISO);
ObjectSetInteger(0,m_name,OBJPROP_WIDTH,0);
ObjectSetInteger(0,m_name,OBJPROP_STYLE,styleISO);
ObjectSetDouble(0,m_name,OBJPROP_PRICE,0,m_price);
ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
ObjectSetDouble(0,m_name,OBJPROP_PRICE,1,m_price+1);
ObjectSetInteger(0,m_name,OBJPROP_TIME,1,T(0));
ObjectSetInteger(0,m_name,OBJPROP_RAY_RIGHT,true);
ObjectSetInteger(0,m_name,OBJPROP_RAY_LEFT,true);
ObjectSetInteger(0,m_name,OBJPROP_BACK,true);
ObjectSetInteger(0,m_name,OBJPROP_SELECTABLE,false);
//---
ChartRedraw();
on_event=true; // Verarbeitung von Events erlauben
}
//+------------------------------------------------------------------+
//| Methode: Objekt neu zeichnen |
//+------------------------------------------------------------------+
void C_OBJ_TREND::Redraw()
{
ObjectSetInteger(0,m_name,OBJPROP_TIME,0,T(0));
ObjectSetInteger(0,m_name,OBJPROP_TIME,1,T(0));
ChartRedraw();
}
Es ist sinnvoller, die Z-Achse mithilfe dieser Klasse zu erstellen. Die Möglichkeiten sind minimal, aber sie reichen für die 3D-Modellierung völlig aus.
Klasse асс C_OBJ_TRENDBYANGLE: Objekt "Trendlinie nach dem Winkel"
Direkter Nachfolger der CIGO-Basisklasse.
//| Klasse OBJ_TRENDBYANGLE: Objekt "Trendlinie nach dem Winkel" |
//+------------------------------------------------------------------+
class C_OBJ_TRENDBYANGLE:public CIGO
{
protected:
int m_bar; // Nummer des Balkens für die Bindung des zweiten Ankerpunktes
//---
double SetAngle(double def)
{
if(m_angle==NULL) // wenn es keinen Wert der Variablen gibt
m_angle=ObjectGetDouble(0,m_name,OBJPROP_ANGLE);
if(m_angle==NULL) // wenn es keine Koordinaten gibt
m_angle=def; // Standardwert
return(m_angle);
}
public:
C_OBJ_TRENDBYANGLE();
virtual // Methode: Objekt erstellen
void Create(string name,double price,double angle);
virtual // Methode: Objekt neu zeichnen
void Redraw();
virtual // Methode für die Verarbeitung des Ereignisses OnChartEvent
bool OnEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam,
int iparamemr=0,
double dparametr=0.0);
};
//+------------------------------------------------------------------+
//| Konstruktor |
//+------------------------------------------------------------------+
C_OBJ_TRENDBYANGLE::C_OBJ_TRENDBYANGLE()
{
S m_bar=c_bar;
}
//+------------------------------------------------------------------+
//| Methode: Objekt erstellen |
//+------------------------------------------------------------------+
void C_OBJ_TRENDBYANGLE::Create(string name,double price,double angle)
{
datetime time=T(0);
datetime deltaT=T(m_bar)-time;
m_name=name;
m_price=SetPrice(price);
m_angle=SetAngle(angle);
ObjectCreate(0,m_name,OBJ_TRENDBYANGLE,0,time,m_price,time+deltaT,m_price);
ObjectSetInteger(0,m_name,OBJPROP_COLOR,clrISO);
ObjectSetInteger(0,m_name,OBJPROP_WIDTH,0);
ObjectSetInteger(0,m_name,OBJPROP_STYLE,styleISO);
ObjectSetInteger(0,m_name,OBJPROP_RAY_RIGHT,true);
ObjectSetInteger(0,m_name,OBJPROP_RAY_LEFT,true);
ObjectSetInteger(0,m_name,OBJPROP_BACK,true);
ObjectSetInteger(0,m_name,OBJPROP_SELECTABLE,true);
//--- ändern wir den Steigungswinkel der Trendlinie; bei der Änderung des Winkels wird die Koordinate des zweiten
//--- Punktes der Linie entsprechend dem neuen Winkelwert neu definiert
ObjectSetDouble(0,m_name,OBJPROP_ANGLE,m_angle);
ChartRedraw();
//---
on_event=true; // Verarbeitung von Events erlauben
}
//+------------------------------------------------------------------+
//| Methode: Objekt neu zeichnen |
//+------------------------------------------------------------------+
void C_OBJ_TRENDBYANGLE::Redraw()
{
ObjectSetInteger(0,m_name,OBJPROP_TIME,T(0));
ObjectSetInteger(0,m_name,OBJPROP_TIME,1,T(0)+T(m_bar)-T(0));
ObjectSetDouble(0,m_name,OBJPROP_PRICE,m_price);
ObjectSetDouble(0,m_name,OBJPROP_ANGLE,m_angle);
ChartRedraw();
}
//+------------------------------------------------------------------+
//| Methode zur Verarbeitung des Ereignisses OnChartEvent |
//+------------------------------------------------------------------+
bool C_OBJ_TRENDBYANGLE::OnEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam,
int iparamemr=0,
double dparametr=0.0)
{
//---
bool res=false;
if(on_event) // Verarbeitung der Ereignisse erlaubt
{
// Löschen des grafischen Objekts
if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DELETE && sparam==m_name)
{
Create(m_name,m_price,m_angle);
}
// Änderung der Chartgröße oder Änderung der Eigenschaften über den Eigenschaftendialog
if((ENUM_CHART_EVENT)id==CHARTEVENT_CHART_CHANGE)
{
Redraw();
}
// Bewegung des grafischen Objekts
if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_DRAG)
{
//---
if(ObjectGetInteger(0,sparam,OBJPROP_SELECTED)==1 && sparam==m_name)
{
m_angle=ObjectGetDouble(0,m_name,OBJPROP_ANGLE);
Create(m_name,m_price,m_angle);
res=true; // benachrichtigen, dass der Ankerpunkt geändert wurde
}
if(iparamemr==Event_1)// Benachrichtigung über die Änderung des Ankerpunkts erhalten
{
m_price=dparametr;
Create(m_name,m_price,m_angle);
}
if(iparamemr==Event_2)// Benachrichtigung über die Änderung des Ankerpunkts erhalten
{
m_angle=dparametr;
Create(m_name,m_price,m_angle);
}
}
// Mausklick nicht auf diesem grafischen Objekt
if((ENUM_CHART_EVENT)id==CHARTEVENT_OBJECT_CLICK && sparam!=m_name)
{
ObjectSetInteger(0,m_name,OBJPROP_SELECTED,0);
ChartRedraw();
}
}
return(res);
}
Ursprünglich war geplant, Nachfolger der Klasse "Trendlinie" für die X- und Y-Achsen zu verwenden, aber im Laufe der Arbeit wurde es klar, dass die "Trendlinie nach dem Winkel" für diese Ziele ideal passt. Beim Durchlesen der MQL5 Dokumentation habe ich dieses mächtige Tool für mich entdeckt: nachdem ich die Trendlinie nach dem Winkel gezeichnet hatte, blieb diese bei der Änderung der Timeframe oder des Maßstabs unverändert, d.h. der Winkel änderte sich nicht.
Schlussfolgerung: Lesen Sie die Dokumentation durch, und Sie entdecken eine Vielzahl nützlicher Funktionen und Tools.
Grafischer Speicher (Graphical Memory)
Der grafischen Speicher wurde bereits im Artikel "Statistische Verteilung in Form eines Histogramms ohne Indikator-Puffer und Arrays" beschrieben und anhand Beispiele veranschaulicht. Ich wiederhole hier nur das grundlegende Prinzip: in den Eigenschaften grafischer Objekte kann man Informationen für die weitere Verwendung in anderen Objekten oder Funktionen des Programms speichern.
Zwecks der Bequemlichkeit und der praktischen Anwendung des grafischen Speichers wurde eine spezielle Klasse erstellt:
{
private:
public:
string m_name; // Name des grafischen Objekts
CGM(){}
~CGM(){}
void Create(string name) {m_name=name;}
// Lesen der Eigenschaft OBJPROP_PRICE
double R_OBJPROP_PRICE(int prop_modifier=0)
{
return(ObjectGetDouble(0,m_name,OBJPROP_PRICE,prop_modifier));
}
// Lesen der Eigenschaft OBJPROP_TIME
datetime R_OBJPROP_TIME(int prop_modifier=0)
S {
return((datetime)ObjectGetInteger(0,m_name,OBJPROP_TIME,prop_modifier));
}
// Lesen der Eigenschaft OBJPROP_ANGLE
double R_OBJPROP_ANGLE()
{
return(ObjectGetDouble(0,m_name,OBJPROP_ANGLE));
}
// Lesen der Eigenschaft OBJPROP_TEXT
string R_OBJPROP_TEXT()
{
return(ObjectGetString(0,m_name,OBJPROP_TEXT));
}
// gibt den Preis für die angegebene Zeit zurück
double R_ValueByTime(datetime time)
{
return(ObjectGetValueByTime(0,m_name,time));
}
// Lesen der Eigenschaft OBJPROP_TEXT
void W_OBJPROP_TEXT(string text)
{
ObjectSetString(0,m_name,OBJPROP_TEXT,text);
}
};
Wie man sieht, sind Methoden zum Lesen und Schreiben von Eigenschaften grafischer Objekte im Code vorhanden. Diese Klasse ist sehr praktisch, denn sie wird bei der Erstellung an ein grafisches Objekt gebunden und ermöglicht es, die notwendigen Informationen für beliebige interaktive grafische Objekte schnell zu erhalten/zu übermitteln. Zum Beispiel:
- es wurde das 3D-Objekt А erstellt, an ihn wurde die Instanz der Klasse des grafischen Speichers AA gebunden;
- nun wird das 3D-Objekt B erstellt, das über AA Information über das Objekt A erhalten kann. Die Objekte A und B sind nicht miteinander verbunden, aber über die Instanz der Klasse AA werden wichtige Daten von A zu B übermittelt;
- bei der Erstellung des Objekts B wird eine Instanz der Klasse des grafischen Speichers BB erstellt. Nun hat das Objekt A Zugang zur Information des Objekts B usw.;
- Es kann beliebig viele Objekte geben, jedes Objekt kann dabei die notwendigen Informationen über Klasseninstanzen des grafischen Speichers empfangen und übertragen.
Man kann sich den grafischen Speicher als das Schwarze Brett vorstellen, auf welchem Gebote und Gesuche nach Informationen platziert sind. Auf diese Weise erfolgt der Datenaustausch zwischen interaktiven grasfischen Objekten. Die notwendigen Daten für eine korrekte Arbeit des 3D-Objekts werden bei der Entwicklung eines 3D-Modell programmiert. Über besonders wertvolle Informationen verfügen die Objekte des Koordinatensystems, denn bei der Drehung oder bei der Verschiebung des Koordinatenzentrums müssen alle 3D-Objekte ihre Eigenschaften und die Position im dreidimensionalen Raum ändern.
Schauen Sie sich an, wie der grafische Speicher in der Klasse des interaktiven Koordinatensystems CUSC verwendet wird.
Koordinatensystem
Die dreidimensionale visuelle Analyse erlaubt es, die Daten im 3D-Raum zu analysieren: zum Beispiel, Sequenzen von Eingabedaten (Beobachtungen) für eine oder mehrere ausgewählte Variablen visuell darstellen. Die ausgewählten Variablen werden an der Y-Achse dargestellt, die Reihen von Beobachtungen — an der X-Achse, und die Werte der Variablen werden an die Z-Achse projiziert.
Solche dreidimensionalen Charts werden für die Visualisierung der Sequenzen von Werten mehrerer Variablen verwendet.
Der wichtigste Vorteil der dreidimensionalen Darstellung gegenüber den zweidimensionalen linearen Charts besteht darin, das es für mehrere Massendaten einfacher ist, auf einem dreidimensionalen Bild einzelne Sequenzen von Werten zu erkennen. Bei der Auswahl eines passenden Blickwinkels z. B. anhand einer interaktiven Drehung, werden sich die Grafiklinien nicht überlappen bzw. zusammenfallen, wie es häufig auf linearen zweidimensionalen Charts der Fall ist.
Bevor wir mit der 3D-Modellierung beginnen, schauen wir uns die Koordinatensysteme an, mit welchen wir Arbeiten werden. Zwei Koordinatensysteme, "Grafik" und "Bild", wurden von Entwicklern der MQL5-Programmiersprache erstellt, und das dreidimensionale System (Axonometrie) setzen wir selbständig um. Gehen wir auf die Unterschiede und den Verwendungszweck jedes Systems ein.
Abb. 2. Das Koordinatensystem "Grafik".
Das Koordinatensystem "Grafik" (Abb. 2). Dies ist ein zweidimensionales Koordinatensystem für Anzeige von Preisdaten, Timeframes und Indikatoren. An der waagerechten Achse liegt die Zeitskala (time), gerichtet von links nach rechts, an der senkrechten Skala — Preis (price) eines Finanzinstruments. Gerade in diesem Koordinatensystem arbeiten die meisten grafischen MQL5-Objekte. Seine Besonderheit besteht darin, dass der aktuelle Preis und Balken an derselben senkrechten Achse liegen. Beim Auftreten eines neuen Balkens wird der Chart automatisch nach links verschoben.
Abb. 3. Das Koordinatensystem "Bild".
Das Koordinatensystem "Bild" (Abb. 3). Jedem Punkt auf dem Bildschirm entspricht ein Pixel. Die Koordinaten der Punkte werden von der linken oberen Ecke der Grafik berechnet. Das System ist genauso wie das System "Grafik" zweidimensional und wird von den Objekten verwendet, die an Timeseries nicht gebunden sind. Dieses Koordinatensystem brauchen wir im Rahmen des Artikels nicht.
Abb. 4. Dreidimensionales Koordinatensystem.
Dreidimensionales Koordinatensystem (Abb. 4). In diesem Koordinatensystem liegen drei Achsen senkrecht zueinander. Aber visuell sind die Winkel zwischen den Achsen XYZ im Koordinatensystem "Bild" nicht gleich 90°. Die X-Achse und Y-Achse bilden, z.B. einen Winkel von 120°. Dieser Winkel kann auch einen anderen Wert haben, aber im Rahmen dieses Artikels lassen wir in so.
Die Z-Achse ist an den aktuellen Balken gebunden, der Maßstab der Achse fällt mit der Skala "price" im Koordinatensystem "Grafik" zusammen. Das ist sehr bequem, denn man muss keine separate Skala für die Z-Achse erstellen, und einfach die Skala "price" verwenden, dabei Z=price.
Die X-Achse ist von rechts nach links gerichtet, d.h. gegenüber der "time" Skala im Koordinatensystem "Grafik". An diese Achse werden wir den Wert der Variablen Bar projizieren. D.h. die Projektion jedes Balkens an die X-Achse werden mit seinem Wert (X=Bar) übereinstimmen.
Die Y-Achse ist für die Reihen zweidimensionaler Daten XZ vorgesehen. Man kann zum Beispiel die Linien der Timeseries Open, Close, High und Low an dieser Achse auseinanderschieben, und sie werden jede in ihrer Ebene liegen. Wenn man alle Punkte dieser Linien in den Ebenen verbindet, die zur YZ-Ebene parallel sind, bekommen wir ein Gewebe der Oberflächen, mit anderen Worten ein dreidimensionales Objekt der Timeseries (s. Abb. 5).
Abb. 5. Ein 3D-Objekt in einem dreidimensionalen Koordinatensystem.
Interaktives Koordinatensystem: CUSC Klasse
Direkter Nachfolger der CIGO-Basisklasse.
{
private:
datetime m_prev_bar;
datetime m_next_bar;
//---
C_OBJ_ARROW_RIGHT_PRICE Centr; // Deklaration der Instanz der Klasse C_OBJ_ARROW_RIGHT_PRICE
C_OBJ_TREND AxisZ; // Deklaration der Instanz der Klasse C_OBJ_TREND
C_OBJ_TRENDBYANGLE AxisY,AxisX; // Deklaration der Instanz der Klasse C_OBJ_TRENDBYANGLE
//---
CGM gCentr; // Deklaration der Instanz der Klasse CGM gAxisY,gAxisX; // Deklaration der Instanz der Klasse CGM
public:
CUSC();
~CUSC();
//--- Berechnung der Z-Koordinate
sPointCoordinates Z(double price, // Preis im Koordinatensystem "Grafik"
int barX, // Verschiebung an der X-Achse
int barY); // Verschiebung an der Y-Achse
//--- neuer Balken
bool on_bar()
{
m_next_bar=T(0);
if(m_next_bar>m_prev_bar)
{
m_prev_bar=m_next_bar;
return(true);
}
return(false);
}
//---
virtual // Methode: IGO erstellen
void Create(string name);
virtual // Methode zur Verarbeitung des Ereignisses OnChartEvent
void OnEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam);
};
Wir schauen uns nur drei Methoden dieser Klasse an: Create(), OnEvent() und Z().
Methode Create: Erstellung eines dreidimensionalen Koordinatensystems
Erstellt ein 3D-Objekt des interaktiven Koordinatensystems. Die Interaktivität bedeutet die Drehung des Koordinatensystems um die Z-Achse und die Verschiebung des Koordinatenursprungs.
{
//--- Zentrum des dreidimensionalen Koordinatensystems
Centr.Create("Axis XYZ"); // erstellen wir das Zentrum des Koordinatensystems
gCentr.Create(Centr.m_name); // erstellen wir ein Objekt des grafischen Speichers
m_price=gCentr.R_OBJPROP_PRICE();
//--- Z-Achse
AxisZ.Create("Axis Z"); // erstellen wir die Z-Achse des Koordnatensystems
//--- Y-Achse
AxisY.Create("Axis Y", // erstellen wir die Y-Achse des Koordnatensystems
gCentr.R_OBJPROP_PRICE(), // erhalten wir den Wert aus dem GM
30); // setzten wir den Steigungswinkel auf 30°
gAxisY.Create(AxisY.m_name); // erstellen wir ein Objekt des grafischen Speichers
m_angle=gAxisY.R_OBJPROP_ANGLE();
//--- X-Achse
AxisX.Create("Axis X", // erstellen wir die X-Achse des Koordnatensystems
gCentr.R_OBJPROP_PRICE(), // erhalten wir den Wert aus dem GM
gAxisY.R_OBJPROP_ANGLE()+ISO); // erhalten wir den Wert aus dem GM und erhöhen ihn um ISO Grad
gAxisX.Create(AxisX.m_name); // erstellen wir ein Objekt des grafischen Speichers
//---
ChartRedraw();
on_event=true; // Verarbeitung von Events erlauben
}
Parameter:
name
[in] Name des Koordinatensystems.
Rückgabewert:
Kein Rückgabewert. Wenn erfolgreich wird ein interaktives Koordinatensystem erstellt.
Methode OnEvent: Verarbeitung eingehender Ereignisse
Verarbeitet Ereignisse, die bei Operationen mit Charts vom Kundenterminal eingehen. Die Methode reagiert nur auf ein Standardereignis: Bewegung des grafischen Objekts. Andere Ereignisse werden weiter an alle Instanzen der Klasse übermittelt, die beim Programmieren des Koordinatensystems erstellt wurden.
const long &lparam,
const double &dparam,
const string &sparam)
{
if(on_event) // Verarbeitung der Ereignisse erlaubt
{
//--- Übertragen der Ereignisse OnChartEvent
AxisZ.OnEvent(id,lparam,dparam,sparam);
//---
if(Centr.OnEvent(id,lparam,dparam,sparam))
{// Bewegung des grafischen Objekts
AxisY.OnEvent(id,lparam,dparam,sparam,Event_1,gCentr.R_OBJPROP_PRICE());
AxisX.OnEvent(id,lparam,dparam,sparam,Event_1,gCentr.R_OBJPROP_PRICE());
}
else
{
if(AxisY.OnEvent(id,lparam,dparam,sparam))
{// Änderung des Winkels des grafischen Objekts
AxisX.OnEvent(id,lparam,dparam,sparam,Event_2,gAxisY.R_OBJPROP_ANGLE()+ISO);
}
else
{
if(AxisX.OnEvent(id,lparam,dparam,sparam))
{// Änderung des Winkels des grafischen Objekts
AxisY.OnEvent(id,lparam,dparam,sparam,Event_2,gAxisX.R_OBJPROP_ANGLE()-ISO);
}
}
}
ChartRedraw();
m_price=gCentr.R_OBJPROP_PRICE();
m_angle=gAxisY.R_OBJPROP_ANGLE();
}
}
Parameter:
id
[in] Eventbezeichner. Es gibt 9 Typen von Ereignissen, die mithilfe dieser Methode verarbeitet werden können.
lparam
[in] Parameter des Events vom Typ long.
dparam
[in] Parameter des Events vom Typ double.
sparam
[in] Parameter des Events vom Typ string.
iparametr
[in] Bezeichner eines benutzerdefinierten Ereignisses.
dparametr
[in] Parameter des Events vom Typ double.
Rückgabewert:
Kein Rückgabewert.
Struktur für das Erhalten von Koordinatenwerten (sPointCoordinates).
Eine Struktur für das Speichern von Koordinatenwerten im Koordinatensystem "Grafik". Dient zum Erhalten der Koordinaten eines 3D-Punktes.
{
datetime time; // Koordinate im System "Grafik"
double price; // Koordinate im System "Grafik"
};
Die Variable vom Typ sPointCoordinates ermöglicht es, mit einem Aufruf der Z() Funktion Koordinatenwerte des 3D-Puntkes im Koordinatensystem "Grafik" zu erhalten.
Methode Z: Berechnung der Z-Koordinate.
Die Methode berechnet die Z-Koordinate.
{
sPointCoordinates res;
res.price=0;
res.time=0;
double dX,dY;
dX=0;
dX=gAxisX.R_ValueByTime(T(barX))-m_price;
dY=0;
dY=gAxisY.R_ValueByTime(T(-barY))-m_price;
res.price=price+dX-dY;
res.time=T(barX-barY);
return(res);
}
Parameter:
price
[in] Z-Koordinate des 3D-Objekts.
barX
[in] X-Koordinate des 3D-Objekts. Der Wert in Balken festgelegt.
barY
[in] Y-Koordinate des 3D-Objekts. Der Wert in Balken festgelegt.
Rückgabewert:
Wenn erfolgreich, gibt den Wert der Variablen vom Typ sPointCoordinates zurück.
Ein Beispiel für die Erstellung einer 3D-Oberfläche als Gitter
Schauen wir und die Zeichnung einer dreidimensionale Oberfläche als Beispiel an:
#define StepX 10 // Schritt an der Х-Achse [bar]
#define StepY 5 // Schritt an der Y-Achse [bar]
#define _X 50 // Anzahl der Punkte an der Х-Achse
#define _Y 15 // Anzahl der Punkte an der Y-Achse
#define W1 1 // Linienbreite
#define W2 3 // Breite der Randlinien
//--- Klassendateien hinzufügen
#include <3D\USC.mqh>
//---
#property indicator_chart_window
//--- Anzahl der Puffer für die Berechnung des Indikators
#property indicator_buffers 0
//--- Anzahl grafischer Reihen im Indikator
#property indicator_plots 0
//---
CUSC USC;
double fun[_X][_Y];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
USC.Create("3D");
//---
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| 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[])
{
ArraySetAsSeries(close,true);
//--- Funktion der 3D-Fläche
for(int i=0;i<_X;i++)
for(int j=0;j<_Y;j++)
{
fun[i][j]=close[i*StepX]-_Point*j*j;
//fun[i][j]=close[i*StepX]-_Point*j*j*i/7;
}
////--- X-Linien
for(int i=1;i<_X;i++)
for(int j=0;j<_Y;j++)
{
sPointCoordinates a0=USC.Z(fun[(i-1)][j], // Funktion
(i-1)*StepX, // X
-j*StepY); // Y
sPointCoordinates a1=USC.Z(fun[i][j], // Funktion
i*StepX, // X
-j*StepY); // Y
string name="line x "+"x"+(string)i+"y"+(string)+j;
ObjectCreate(0,name,OBJ_TREND,0,a0.time,a0.price,a1.time,a1.price);
if(fun[i][j]>USC.m_price && fun[i-1][j]>USC.m_price)
ObjectSetInteger(0,name,OBJPROP_COLOR,clrRed);
else
ObjectSetInteger(0,name,OBJPROP_COLOR,clrBlue);
ObjectSetInteger(0,name,OBJPROP_WIDTH,W1);
if(j==0 || j==_Y-1)
ObjectSetInteger(0,name,OBJPROP_WIDTH,W2);
ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
ObjectSetInteger(0,name,OBJPROP_BACK,true);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
}
////--- Y-Linien
for(int i=0;i<_X;i++)
for(int j=1;j<_Y;j++)
{
sPointCoordinates a0=USC.Z(fun[i][j-1], // Funktion
i*StepX, // X
-(j-1)*StepY); // Y
sPointCoordinates a1=USC.Z(fun[i][j], // Funktion
i*StepX, // X
-j*StepY); // Y
string name="line y "+"x"+(string)i+"y"+(string)+j;
ObjectCreate(0,name,OBJ_TREND,0,a0.time,a0.price,a1.time,a1.price);
ObjectSetInteger(0,name,OBJPROP_COLOR,clrGreen);
ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
ObjectSetInteger(0,name,OBJPROP_BACK,true);
ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
}
//--- return value of prev_calculated for next call
return(rates_total);
}
//+------------------------------------------------------------------+
//| ChartEvent function |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
const long &lparam,
const double &dparam,
const string &sparam)
{
USC.OnEvent(id,lparam,dparam,sparam);
}
Die Linien in den Ebenen, die parallel zu den XZ und YZ Ebenen sind, werden getrennt gezeichnet. Man kann die Anzahl der Punkte an jeder Achse und die Funktion selbst variieren.
Abb. 6. Ein Beispiel für das Zeichnen einer 3D-Fläche.
Abb. 7. Ein Beispiel für das Zeichnen des 3D Moving Average Indikators.
Die Antwort auf die Frage von Skeptikern: Wofür braucht man die 3D-Modellierung und wie kann diese im Handel helfen?
Natürlich, ist das MetaTrader 5 Terminal für den Handel auf den Finanzmärkten vorgesehen. Für Händler und Entwickler automatischer Handelsstrategien interessieren in erster Linie Handelsalgorithmen und die Entwicklung profitabler Strategien. Die im Artikel beschriebene Richtung steckt noch in den Anfängen und liefert noch keine konkreten Ergebnisse für den Handel, aber die 3D-Modellierung kann bei der Präsentation Ihrer Handelsideen und Strategien den Investoren hilfreich sein. Darüber hinaus werden dynamische 3D-Modelle im Terminal erstellt, die auf das Eingehen von Marktinformationen reagieren und sich in der Zeit deformieren (ihren Körper verformen), dies ermöglicht die Erstellung von 3D-Indikatoren.
Als Pro-Argument möchte ich einen Vergleich zu Windows ziehen: die ersten Versionen des Systems waren ungeschickt und langsam. Die Anhänger von Norton Commander waren zur Idee grafischer Interfaces skeptisch eingestellt. Und wo ist nun NC?
Fazit
- Die 3D-Modellierung ist eine schwierige Aufgabe in jeder Programmierungsumgebung, aber die Entwickler der MQL5-Programmiersprache waren der Zeit einen Schritt voraus und haben ihren Kunden leistungsstarke Funktionen für die Umsetzung der dreidimensionalen Visualisierung im Handelsterminal angeboten.
- Wir haben die ersten Ergebnisse der Programmierung einer Bibliothek der Klassen für 3D-Objekte erhalten. Das Thema ist so groß, dass ein Artikel nicht alle Möglichkeiten und Perspektiven der 3D-Modellierung umfassen kann. Ich hoffe, es finden sich Nachfolger, die Wege für die Entwicklung der 3D-Richtung im Trading und in der Programmierung in MQL5 vorschlagen können.
- Das erstellte Werkzeug der 3D-Modellierung kann die Entwicklung einer neuen Richtung in der technischen Analyse wesentlich beeinflussen: Es geht um 3D-Indikatoren und deren Analyse.
- Es gibt Probleme mit dem Rendering, aber an dieser Etappe der Entwicklung der Bibliothek ist das nicht so dramatisch.
- Der im Artikel vorgestellte Grundsatz für das Zeichnen von 3D-Objekten kann auch höchstwahrscheinlich auf OpenCL übertragen werden.
Hinweis:
Die Dateien der Bibliothek müssen in den Ordner ..\Include\3D kopiert werden.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2828





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