English Русский 中文 Español 日本語 Português
preview
Der Indikator Market Profile (Teil 2) Optimierung und Rendering auf Leinwand

Der Indikator Market Profile (Teil 2) Optimierung und Rendering auf Leinwand

MetaTrader 5Beispiele |
143 5
Artyom Trishkin
Artyom Trishkin

Inhalt


Einführung

Im letzten Artikel haben wir uns mit dem Marktprofil-Indikator befasst. Wie sich herausstellt, verbraucht die Erstellung eines Marktprofils mit gewöhnlichen grafischen Objekten ziemlich viele Ressourcen. Jeder Preispunkt zwischen Tief und Hoch des Tagesbalkens wird mit rechteckigen grafischen Objekten in der Anzahl der Balken gefüllt, die dieses Preisniveau im Laufe des Tages erreicht haben. Dies gilt für jede Position - sie alle enthalten viele grafische Objekte, und alle diese Objekte werden für jeden Tag, an dem das Profildiagramm gezeichnet wird, erstellt und gezeichnet. Wenn ein Indikator Tausende von grafischen Objekten erstellt, kann dies zu erheblichen Verlangsamungen bei der Handhabung anderer grafischer Objekte und der Neuzeichnung des Charts führen. 

Starten wir den Indikator auf dem M30-Chart und erstellen ein Marktprofil für nur drei Tage:

Das führt zur Erstellung von 4697 rechteckigen grafischen Objekten:

Dies ist wirklich keine optimale Nutzung der Ressourcen. Wenn wir die Anzahl der angezeigten Tage in den Einstellungen erhöhen, steigt die Anzahl der erstellten Objekte, die zum Zeichnen des Marktprofils auf dem Chart für jeden angezeigten Tag verwendet werden, drastisch an.

Aber hier zeichnen wir einfach Charte mit grafischen Objekten - Rechtecken. Ein kurzes Liniensegment des Profilhistogramms ist ein grafisches Objekt. Das bedeutet, dass wir nicht direkt auf dem Chart zeichnen können, sondern nur auf einem grafischen Objekt - einer Leinwand (canvas), die sich wiederum auf dem Chart entlang der gewünschten Koordinaten befindet. Dann haben wir nur ein (!) grafisches Objekt für einen Tag. Und drei Tage lang wird es drei Objekte statt 4697 geben! Das ist ein erheblicher Unterschied! Dies kann mit Hilfe der Klasse CCanvas geschehen, die das Rendering von nutzerdefinierten Bildern vereinfacht, die als Teil der Standardbibliothek des Client-Terminals bereitgestellt werden.

Die Version des Market Profile-Indikators, die das Profilhistogramm auf der Leinwand darstellt, ist im Terminal in der Datei \MQL5\Indikatoren\Freie Indikatoren\, MarketProfile Canvas.mq5 verfügbar. Bei der Untersuchung des Codes können wir feststellen, dass hier, anders als in der ersten Version (MarketProfile.mq5), die Grafikausgabe auf Objekten der Klasse CCanvas erfolgt. Die Logik des Indikators bleibt dieselbe, und wir haben sie bereits im Abschnitt „Struktur und Grundsätze“ des ersten Artikels erörtert. Das Rendering erfolgt mit der Klasse CMarketProfile, die auf CCanvas zeichnet.

Die Funktionsweise ist denkbar einfach:

  • in einer Schleife mit der angegebene Anzahl von Tagen,
    • ein Objekt der Klasse CMarketProfile für den aktuellen Tag in der Schleife erstellen oder abrufen,
      • das Profil des Tages auf der Leinwand zeichnen oder neu zeichnen, das dem aktuellen Tag in der Schleife entspricht.

Die Hauptarbeit beim Zeichnen des Profildiagramms wird innerhalb der Klasse CMarketProfile ausgeführt. Werfen wir einen Blick auf die Struktur und Funktionsweise dieser Klasse.


Die Klasse CMarketProfile

Wir öffnen die Datei \MQL5\Indicators\Free Indicators\MarketProfile Canvas.mq5 und suchen darin den Code der Klasse CMarketProfile. Schauen wir uns an, was es dort gibt, und diskutieren wir, wozu das alles gut sein soll:

//+------------------------------------------------------------------+
//| Class to store and draw Market Profile for the daily bar         |
//+------------------------------------------------------------------+
class CMarketProfile
  {
public:
                     CMarketProfile() {};
                     CMarketProfile(string prefix, datetime time1, datetime time2, double high, double low, MqlRates &bars[]);
                    ~CMarketProfile(void);

   //--- checks if the object was created for the specified date
   bool              Check(string prefix, datetime time);
   //--- set high/low and array of intraday bars
   void              SetHiLoBars(double high, double low, MqlRates &bars[]);
   //--- set canvas dimensions and drawing options
   void              UpdateSizes(void);
   //--- is the profile in the visible part of the chart?
   bool              isVisibleOnChart(void);
   //--- has the graph scale changed?
   bool              isChartScaleChanged(void);
   //--- calculates profile by sessions
   bool              CalculateSessions(void);
   //--- draws a profile
   void              Draw(double multiplier=1.0);
   //---
protected:
   CCanvas           m_canvas;      // CCanvas class object for drawing profile
   uchar             m_alpha;       // alpha channel value that sets transparency
   string            m_prefix;      // unique prefix of the OBJ_BITMAP object
   string            m_name;        // name of the OBJ_BITMAP object used in m_canvas
   double            m_high;        // day's High
   double            m_low;         // day's Low
   datetime          m_time1;       // start time of the day
   datetime          m_time2;       // end time of the day
   int               m_day_size_pt; // daily bar height in points
   int               m_height;      // daily bar height in pixels on the chart
   int               m_width;       // daily bar width in pixels on the chart
   MqlRates          m_bars[];      // array of bars of the current timeframe between m_time1 and m_time2
   vector            m_asia;        // array of bar counters for the Asian session
   vector            m_europe;      // array of bar counters for the European session
   vector            m_america;     // array of bar counters for the American session
   double            m_vert_scale;  // vertical scaling factor
   double            m_hor_scale;   // horizontal scaling factor
  };

Die in der Klasse deklarierten öffentliche Methoden:
  • Mit der Methode Check() wird geprüft, ob ein für einen bestimmten Tag erstelltes Marktprofilobjekt existiert;
  • Die Methode SetHiLoBars() wird verwendet, um die Höchst- und Tiefstpreise des Tages in das Marktprofilobjekt zu setzen und ein Array von Intraday-Balken an das Objekt zu übergeben;
  • Die Methode UpdateSizes() legt die Leinwandabmessungen und Skalierungsfaktoren für das Zeichnen von Rechtecken im Marktprofilobjekt fest;
  • Die Methode isVisibleOnChart() gibt das Flag zurück, das anzeigt, dass das Marktprofil innerhalb der Sichtbarkeit des Charts liegt;
  • Die Methode isChartScaleChanged() ist in der Klasse deklariert, aber nicht implementiert;
  • Die Methode CalculateSessions() berechnet die Parameter und füllt die Arrays der Handelssitzungen;
  • Die Methode Draw() zeichnet ein Marktprofilhistogramm auf der Leinwand, das auf Daten aus allen Handelssitzungen basiert.

Der Zweck von Variablen, die im geschützten Bereich einer Klasse deklariert sind, ist ziemlich klar. Ich möchte auf die Arrays der Zähler der Balken einer Sitzung eingehen.
Alle werden als Vektorvariablen deklariert, was es ermöglicht, sie wie Datenarrays zu behandeln, auch wenn dies etwas einfacher ist:

Die Verwendung von Vektoren und Matrizen oder besser gesagt von speziellen Methoden der entsprechenden Typen ermöglicht die Erstellung eines einfacheren, kürzeren und klareren Codes, der der mathematischen Notation nahe kommt. Mit diesen Methoden müssen wir keine verschachtelten Schleifen erstellen oder auf die korrekte Indizierung von Arrays in Berechnungen achten. Die Verwendung von Matrix- und Vektormethoden erhöht daher die Zuverlässigkeit und Geschwindigkeit bei der Entwicklung komplexer Programme.

Betrachten wir nun die Implementierung der deklarierten Klassenmethoden.

Konstruktor:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
void CMarketProfile::CMarketProfile(string prefix, datetime time1, datetime time2, double high, double low, MqlRates &bars[]):
   m_prefix(prefix),
   m_time1(time1),
   m_time2(time2),
   m_high(high),
   m_low(low),
   m_vert_scale(NULL),
   m_hor_scale(NULL)
  {
//--- copy the array of intraday bars to the array of MqlRates structures,
//--- create a name for the graphical object and define the size of the daily candle
   ArrayCopy(m_bars, bars);
   m_name=ExtPrefixUniq+"_MP_"+TimeToString(time1, TIME_DATE);
   m_day_size_pt=(int)((m_high-m_low)/SymbolInfoDouble(Symbol(), SYMBOL_POINT));
//--- set vector sizes for trading sessions
   m_asia=vector::Zeros(m_day_size_pt);
   m_europe=vector::Zeros(m_day_size_pt);
   m_america=vector::Zeros(m_day_size_pt);
//--- set the width and height of the canvas
   UpdateSizes();
//--- if this is the first tick at the beginning of the day, then the canvas dimensions will be zero - set the dimensions to 1 pixel in height and width
   m_height=m_height?m_height:1;
   m_width=m_width?m_width:1;
//--- create a graphical object
   if(m_canvas.CreateBitmap(m_name, m_time1, m_high, m_width, m_height, COLOR_FORMAT_ARGB_NORMALIZE))
      ObjectSetInteger(0, m_name, OBJPROP_BACK, true);
   else
     {
      Print("Error creating canvas: ", GetLastError());
      Print("time1=", m_time1, "  high=", m_high, "  width=", m_width, "  height=", m_height);
     }
  }

Der parametrische Konstruktor erhält das Präfix des Namens des zu erstellenden Canvas-Objekts (auf dem das Tagesprofil gerendert werden soll), die Start- und Endzeit des Tages, die Höchst- und Tiefstpreise des Tages und ein Array von Intraday-Balken. Die Werte dieser Variablen werden auf die entsprechenden Klassenvariablen im Initialisierungsstring gesetzt. Weiter:

  • wird das als Referenz übergebene Array in das Klassenarray kopiert, ein eindeutiger Name des grafischen Objekts aus dem in den Eingaben des Präfixes, der Abkürzung „_MP_“ und der Tagesöffnungszeit übergebenen erstellt und die Tageskerze in Punkten berechnet;
  • jedes der Arrays der Handelssitzungen erhält eine Größe, die der Größe des Tagesbalkens in Punkten entspricht, und wird gleichzeitig mit Nullen gefüllt - initialisiert;
  • werden die Abmessungen der Leinwand für das Zeichnen des Profils festgelegt, und wenn dies der erste Tick des Tages ist, wird die Größe auf Null gesetzt, und die Breite und Höhe werden auf die minimal zulässigen Abmessungen von einem Pixel in beiden Dimensionen festgelegt;
  • wird eine Zeichenfläche mit den angegebenen Abmessungen erstellt.

Die Methode, mit der geprüft wird, ob ein für einen bestimmten Tag erstelltes Marktprofilobjekt vorhanden ist:

//+------------------------------------------------------------------+
//| Checks if CMarketProfile object is for the specified 'time' date |
//+------------------------------------------------------------------+
bool CMarketProfile::Check(string prefix, datetime time)
  {
   string calculated= prefix+"_MP_"+TimeToString(time, TIME_DATE);
   return (m_name==(calculated));
  };

Da der Name jedes Profil-Canvas-Objekts im Klassenkonstruktor festgelegt wird und der dargestellte Name eine Zeichenkette der Startzeit des Tages verwendet, wird zur Überprüfung, ob das Objekt für eine bestimmte Zeit erstellt wurde, die Startzeit des Tages an die Methode übergeben, eine Zeichenkette erstellt, der mit der Zeichenkette des Objektnamens identisch ist, und die erstellte Zeichenkette wird mit dem tatsächlichen Namen des Objekts verglichen. Das Ergebnis der Prüfung wird von der Methode zurückgegeben.

Die Methode zum Festlegen der Tageshöchst- und -tiefstpreise in einem Marktprofilobjekt und zur Übergabe eines Arrays von Intraday-Balken an das Objekt:

//+------------------------------------------------------------------+
//| Sets High/Low and a set of current-timeframe bars                |
//+------------------------------------------------------------------+
void CMarketProfile::SetHiLoBars(double high, double low, MqlRates &bars[])
  {
//--- if the maximum of the day has changed, move the OBJ_BITMAP object to the new Y coordinate
   if(high>m_high)
     {
      m_high=high;
      if(!ObjectSetDouble(0, m_name, OBJPROP_PRICE, m_high))
         PrintFormat("Failed to update canvas for %s, error %d", TimeToString(m_time1, TIME_DATE), GetLastError());
     }
   ArrayCopy(m_bars, bars);
   m_high=high;
   m_low=low;
//--- daily range in points
   m_day_size_pt=(int)((m_high-m_low)/SymbolInfoDouble(Symbol(), SYMBOL_POINT));
//--- reset vector sizes for trading sessions
   m_asia=vector::Zeros(m_day_size_pt);
   m_europe=vector::Zeros(m_day_size_pt);
   m_america=vector::Zeros(m_day_size_pt);
  }

Die Methode empfängt die Hoch- und Tiefstwerte der Tageskerze sowie ein Array von Intraday-Balken im Format der Struktur von MqlRates als Referenz.

  • In die Objektvariable wird der Höchstpreis geschrieben und die Leinwand wird an eine neue Koordinate verschoben.
  • Die Intraday-Balken werden aus dem übergebenen Array von Balken in das interne Array kopiert.
  • Das Tief des Tages wird auf die Klassenvariable gesetzt.
  • Die neue Größe des Tagesbalken wird in Punkten berechnet.
  • Die Arrays der Handelssitzungen werden um den berechneten Wert der Größe des Tagesbalkens in Punkten erhöht und mit Nullen gefüllt - initialisiert.

Es ist zu beachten, dass die Matrix- und Vektormethode Zeros() zur Initialisierung der Vektoren verwendet wird. Die Methode legt sowohl die Größe des Vektors fest als auch füllt das gesamte Array mit Nullen auf.
Für ein einfaches Array müssten wir zwei Operationen durchführen: ArrayResize() und ArrayInitialize().

Die Methode zum Festlegen der Leinwandabmessungen und Skalierungsfaktoren für das Zeichnen von Rechtecken im Marktprofilobjekt:

//+------------------------------------------------------------------+
//|  Sets drawing parameters                                         |
//+------------------------------------------------------------------+
void CMarketProfile::UpdateSizes(void)
  {
//--- convert time/price to x/y coordinates
   int x1, y1, x2, y2;
   ChartTimePriceToXY(0, 0, m_time1, m_high, x1, y1);
   ChartTimePriceToXY(0, 0, m_time2, m_low,  x2, y2);
//--- calculate canvas dimensions
   m_height=y2-y1;
   m_width =x2-x1;
//--- calculate ratios for transforming vertical price levels
//--- and horizontal bar counters to chart pixels
   m_vert_scale=double(m_height)/(m_day_size_pt);
   m_hor_scale =double(m_width*PeriodSeconds(PERIOD_CURRENT))/PeriodSeconds(PERIOD_D1);
   
//--- change the canvas size
   m_canvas.Resize(m_width, m_height);
  }

Die Logik der Methode ist im Code kommentiert. Skalierungsverhältnisse werden verwendet, um die Größe der auf der Leinwand gezeichneten Rechtecke auf der Grundlage des Verhältnisses zwischen der Leinwandgröße und der Größe des Chartfensters festzulegen.
Die berechneten Verhältnisse werden bei der Berechnung der Höhe und Breite der gerenderten Rechtecke berücksichtigt.

Die Methode, die ein Flag zurückgibt, das besagt, dass das Marktprofil innerhalb der Chart-Sichtbarkeit liegt:

//+------------------------------------------------------------------+
//|  Checks that the profile is in the visible part of the chart     |
//+------------------------------------------------------------------+
bool CMarketProfile::isVisibleOnChart(void)
  {
   long last_bar=ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR);        // last visible bar on the chart on the left
   long first_bar=last_bar+-ChartGetInteger(0, CHART_VISIBLE_BARS);  // first visible bar on the chart on the right
   first_bar=first_bar>0?first_bar:0;
   datetime left =iTime(Symbol(), Period(), (int)last_bar);          // time of the left visible bar on the chart
   datetime right=iTime(Symbol(), Period(), (int)first_bar);         // time of the right visible bar on the chart
   
//--- return a flag that the canvas is located inside the left and right visible bars of the chart
   return((m_time1>= left && m_time1 <=right) || (m_time2>= left && m_time2 <=right));
  }

Hier finden wir die Nummern der linken und rechten sichtbaren Balken auf dem Chart, ermitteln ihre Zeit und geben das Flag zurück, dass die Zeit des linken und rechten Rands der Leinwand innerhalb des Bereichs der sichtbaren Balken auf dem Chart liegt.

Die Methode, die die Parameter berechnet und die Arrays der Handelssitzungen füllt:

//+------------------------------------------------------------------+
//| Prepares profile arrays by sessions                              |
//+------------------------------------------------------------------+
bool CMarketProfile::CalculateSessions(void)
  {
   double point=SymbolInfoDouble(Symbol(), SYMBOL_POINT);   // one point value
//--- if the array of intraday bars is not filled, leave
   if(ArraySize(m_bars)==0)
      return(false);
//---- iterate over all the bars of the current day and mark the cells of the arrays (vectors) that contain the bars being iterated over in the loop
   int size=ArraySize(m_bars);
   for(int i=0; i<size; i++)
     {
      //--- get the bar hour
      MqlDateTime bar_time;
      TimeToStruct(m_bars[i].time, bar_time);
      uint        hour     =bar_time.hour;
      //--- calculate price levels in points from the Low of the day reached by the price on each bar of the loop
      int         start_box=(int)((m_bars[i].low-m_low)/point);   // index of the beginning of price levels reached by the price on the bar
      int         stop_box =(int)((m_bars[i].high-m_low)/point);  // index of the end of price levels reached by the price on the bar

      //--- American session
      if(hour>=InpAmericaStartHour)
        {
         //--- in the loop from the beginning to the end of price levels, fill the counters of bars where the price was at this level
         for(int ind=start_box; ind<stop_box; ind++)
            m_america[ind]++;
        }
      else
        {
         //--- European session
         if(hour>=InpEuropeStartHour && hour<InpAmericaStartHour)
            //--- in the loop from the beginning to the end of price levels, fill the counters of bars where the price was at this level
            for(int ind=start_box; ind<stop_box; ind++)
               m_europe[ind]++;
         //--- Asian session
         else
            //--- in the loop from the beginning to the end of price levels, fill the counters of bars where the price was at this level
            for(int ind=start_box; ind<stop_box; ind++)
               m_asia[ind]++;
        }
     }
//--- vectors of all sessions are ready
   return(true);
  }

Im vorangegangenen Artikel haben wir uns eingehend mit der Logik der Definition der Anzahl der Balken in einer Handelssitzung befasst, deren Kurs die Niveaus in Punkten vom Tiefst- bis zum Höchstkurs des Tages erreicht hat. Wurde in der vorherigen Version des Indikators all dies in der Hauptschleife des Indikators durchgeführt, so wird hier diese gesamte Berechnung in eine separate Methode des Tagesprofilobjekts ausgelagert. Hier geht es darum, die Anzahl der Balken zu zählen und in die Zellen des Arrays (Vektors) zu schreiben, die jedes in Punkten berechnete Preisniveau vom Tiefst- bis zum Höchststand des Tages überschreiten. Nachdem die Methode ihre Arbeit beendet hat, werden alle Vektoren entsprechend der Preisbewegung auf den Preisstufen gefüllt. Die Anzahl der Balken, die jede Ebene überschritten haben, wird in den entsprechenden Zellen des Arrays (Vektors) eingetragen.

Die Methode, die ein Histogramm des Marktprofils auf der Leinwand zeichnet, das auf den Daten aller Handelssitzungen basiert:

//+------------------------------------------------------------------+
//|  Draw Market Profile on the canvas                               |
//+------------------------------------------------------------------+
void CMarketProfile::Draw(double multiplier=1.0)
  {
//--- sum up all sessions for rendering
   vector total_profile=m_asia+m_europe+m_america;   // profile that combines all sessions
   vector europe_asia=m_asia+m_europe;               // profile that combines only the European and Asian sessions

//--- set a completely transparent background for the canvas
   m_canvas.Erase(ColorToARGB(clrBlack, 0));

//--- variables for drawing rectangles
   int x1=0;                           // X coordinate of the left corner of the rectangle always starts at zero
   int y1, x2, y2;                     // rectangle coordinates
   int size=(int)total_profile.Size(); // size of all sessions
   
//--- render the American session with filled rectangles
   for(int i=0; i<size; i++)
     {
      //--- skip zero vector values
      if(total_profile[i]==0)
         continue;
      //--- calculate two points to draw a rectangle, x1 is always 0 (X of the lower left corner of the rectangle)
      y1=m_height-int(i*m_vert_scale);                    // Y coordinate of the lower left corner of the rectangle
      y2=(int)(y1+m_vert_scale);                          // Y coordinate of the upper right corner of the rectangle
      x2=(int)(total_profile[i]*m_hor_scale*multiplier);  // X coordinate of the upper right corner of the rectangle 
      //--- draw a rectangle at the calculated coordinates with the color and transparency set for the American session
      m_canvas.FillRectangle(x1, y1, x2, y2, ColorToARGB(InpAmericaSession, InpTransparency));
     }

//--- render the European session with filled rectangles
   for(int i=0; i<size; i++)
     {
      //--- skip zero vector values
      if(total_profile[i]==0)
         continue;
      //--- calculate two points to draw a rectangle
      y1=m_height-int(i*m_vert_scale);
      y2=(int)(y1+m_vert_scale);
      x2=(int)(europe_asia[i]*m_hor_scale*multiplier);
      //--- draw a rectangle over the rendered American session using the calculated coordinates
      //--- with color and transparency set for the European session
      m_canvas.FillRectangle(x1, y1, x2, y2, ColorToARGB(InpEuropeSession, InpTransparency));
     }

//--- draw the Asian session with filled rectangles
   for(int i=0; i<size; i++)
     {
      //--- skip zero vector values
      if(total_profile[i]==0)
         continue;
      //--- calculate two points to draw a rectangle
      y1=m_height-int(i*m_vert_scale);
      y2=(int)(y1+m_vert_scale);
      x2=(int)(m_asia[i]*m_hor_scale*multiplier);
      //--- draw a rectangle over the rendered European session using the calculated coordinates
      //--- with color and transparency set for the Asian session
      m_canvas.FillRectangle(x1, y1, x2, y2, ColorToARGB(InpAsiaSession, InpTransparency));
     }
//--- update the OBJ_BITMAP object without redrawing the chart
   m_canvas.Update(false);
  }

Die Methodenlogik wurde in den Codekommentaren ausführlich beschrieben. Kurz gesagt, wir haben Arrays (Vektoren) von drei Sitzungen - asiatisch, europäisch und amerikanisch - berechnet und gefüllt. Es ist erforderlich, für jede Sitzung ein Profilhistogramm zu erstellen. Zuerst wird die amerikanische Sitzung gerendert, dann wird die europäische Sitzung darüber gerendert und schließlich wird die asiatische Sitzung über den beiden gezeichneten Sitzungen gerendert.
Warum werden die Sitzungen in umgekehrter Reihenfolge ihrer Laufzeit dargestellt?

  • Die amerikanische Sitzung, oder besser gesagt ihr Histogramm, enthält sowohl die bereits gehandelte Zeit der beiden vorangegangenen Sitzungen als auch die Zeit der amerikanischen Sitzung, d. h. es handelt sich um das vollständigste Histogramm des Profils des gesamten Tages. Aus diesem Grund wird sie zuerst wiedergegeben.
  • Dann wird die europäische Sitzung wiedergegeben, die die Zeit der bereits gehandelten asiatischen Sitzung enthält. Da es hier nur zwei Sitzungen gibt - die asiatische und die europäische - wird das Histogramm auf der X-Achse der amerikanischen Sitzung kürzer sein, was bedeutet, dass es über der amerikanischen Sitzung gerendert werden muss. 
  • Dann wird das kürzeste Histogramm der asiatischen Sitzung entlang der X-Achse wiedergegeben. 
Auf diese Weise werden alle Histogramme der einzelnen Sitzungen in der richtigen Reihenfolge übereinander gelegt, sodass ein vollständiges Bild des gesamten Marktprofils für den Tag entsteht.

Ich möchte anmerken, wie praktisch es ist, Array-Daten bei der Verwendung von Vektoren zu kombinieren:

//--- sum up all sessions for rendering
   vector total_profile=m_asia+m_europe+m_america;   // profile that combines all sessions
   vector europe_asia=m_asia+m_europe;               // profile that combines only the European and Asian sessions

Im Wesentlichen handelt es sich um eine Element-für-Element-Verkettung mehrerer Arrays derselben Größe zu einem einzigen Array, das durch den folgenden Code dargestellt werden kann:

#define SIZE   3

double array_1[SIZE]={0,1,2};
double array_2[SIZE]={3,4,5};
double array_3[SIZE]={6,7,8};

Print("Contents of three arrays:");
ArrayPrint(array_1);
ArrayPrint(array_2);
ArrayPrint(array_3);

for(int i=0; i<SIZE; i++)
  {
   array_1[i]+=array_2[i]+=array_3[i];
  }
  
Print("\nResult of the merge:");
ArrayPrint(array_1);
/*
Contents of three arrays:
0.00000 1.00000 2.00000
3.00000 4.00000 5.00000
6.00000 7.00000 8.00000

Result of the merge:
 9.00000 12.00000 15.00000
*/

Der nachstehende Code bewirkt das Gleiche wie die Codezeile in der oben beschriebenen Methode:

vector total_profile=m_asia+m_europe+m_america;   // profile that combines all sessions

Ich denke, es ist unnötig zu sagen, wie viel bequemer und übersichtlicher der Code ist...

Das erstellte Canvas-Objekt wird im Destruktor der Klasse gelöscht und das Chart wird neu gezeichnet, um die Änderungen anzuzeigen:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
void CMarketProfile::~CMarketProfile(void)
  {
//--- delete all graphical objects after use
   ObjectsDeleteAll(0, m_prefix, 0, OBJ_BITMAP);
   ChartRedraw();
  }

Anstatt mit grafischen Objekten in der Indikatorschleife zu zeichnen, genügt es nun, für jeden Tagesbalken eine Instanz der betrachteten Klasse zu erstellen, die Daten aller Sitzungen zu berechnen und ein Marktprofilhistogramm für jeden Tag auf der Leinwand zu zeichnen. Die Anzahl der grafischen Objekte, die erstellt werden, hängt von der Anzahl der Tage ab, die in den Einstellungen für die Profildarstellung angegeben sind, im Gegensatz zur vorherigen Version des Indikators, bei der jede Linie des Histogramms mit einem eigenen grafischen Objekt gezeichnet wird.


Optimierung des Indikators

Schauen wir uns nun an, wie der Indikator mit Hilfe der Klasse Marktprofil erstellt wird. Öffnen wir die Indikator-Datei \MQL5\Indicators\Free Indicators\MarketProfile Canvas.mq5 von Anfang an und schauen sie uns an.

Zunächst einmal sind die Klassendateien für die vereinfachte Erstellung von nutzerdefinierten Renderings von CCanvas sowie die Klassendatei für die Erstellung von stark typisierten Listen CArrayList<T> enthalten:

//+------------------------------------------------------------------+
//|                                         MarketProfile Canvas.mq5 |
//|                              Copyright 2009-2024, MetaQuotes Ltd |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Canvas\Canvas.mqh>
#include <Generic\ArrayList.mqh>

//--- input parameters

Es folgen die Liste der Eingänge des Indikators, ein eindeutiges Präfix der grafischen Objekte, die angegebene Marktprofilklasse und die angegebene Liste der Klassenobjekte:

//--- input parameters
input uint  InpStartDate       =0;           /* day number to start calculation */  // 0 - current, 1 - previous, etc.
input uint  InpShowDays        =7;           /* number of days to display */        // starting with and including the day in InpStartDate
input int   InpMultiplier      =1;           /* histogram length multiplier */      
input color InpAsiaSession     =clrGold;     /* Asian session */                    
input color InpEuropeSession   =clrBlue;     /* European session */                 
input color InpAmericaSession  =clrViolet;   /* American session */                 
input uchar InpTransparency    =150;         /* Transparency, 0 = invisible */      // market profile transparency, 0 = fully transparent
input uint  InpEuropeStartHour =8;           /* European session opening hour */    
input uint  InpAmericaStartHour=14;          /* American session opening hour */    

//--- unique prefix to identify graphical objects belonging to the indicator
string ExtPrefixUniq;

//--- declare CMarketProfile class
class CMarketProfile;
//--- declare a list of pointers to objects of the CMarketProfile class
CArrayList<CMarketProfile*> mp_list;

Da die Marktprofilklasse unterhalb des Indikatorcodes geschrieben wird, ist eine Vorwärts-Deklaration der Klasse erforderlich, um den Fehler eines unbekannten Variablentyps bei der Kompilierung zu vermeiden:

'CMarketProfile' - unexpected token

Die stark typisierte Liste enthält Zeiger auf Objekte der Klasse CMarketProfile, die unten im Code festgelegt sind.

In OnInit() erstellen wir das Präfix der grafischen Objekte als die letzten 4 Ziffern der Anzahl der Millisekunden, die seit dem Systemstart vergangen sind:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create a prefix for object names
   string number=StringFormat("%I64d", GetTickCount64());
   ExtPrefixUniq=StringSubstr(number, StringLen(number)-4);
   Print("Indicator \"Market Profile Canvas\" started, prefix=", ExtPrefixUniq);

   return(INIT_SUCCEEDED);
  }

Schauen wir uns den vollständigen Code von OnCalculate() an:

//+------------------------------------------------------------------+
//| 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[])
  {
//--- opening time of the current daily bar
   datetime static open_time=0;

//--- number of the last day for calculations
//--- (if InpStartDate = 0 and InpShowDays = 3, lastday = 3)
//--- (if InpStartDate = 1 and InpShowDays = 3, lastday = 4) etc ...
   uint lastday=InpStartDate+InpShowDays;

//--- if the first calculation has already been made
   if(prev_calculated!=0)
     {
      //--- get the opening time of the current daily bar
      datetime current_open=iTime(Symbol(), PERIOD_D1, 0);
      
      //--- if we do not calculate the current day
      if(InpStartDate!=0)
        {
         //--- if the opening time was not received, leave
         if(open_time==current_open)
            return(rates_total);
        }
      //--- update opening time
      open_time=current_open;
      //--- we will only calculate one day from now on, since all other days have already been calculated during the first run
      lastday=InpStartDate+1;
     }

//--- in a loop for the specified number of days (either InpStartDate+InpShowDays on first run, or InpStartDate+1 on each tick)
   for(uint day=InpStartDate; day<lastday; day++)
     {
      //--- get the data of the day with index day into the structure
      MqlRates day_rate[];
      //--- if the indicator is launched on weekends or holidays when there are no ticks, you should first open the daily chart of the symbol
      //--- if we have not received bar data for the day index of the daily period, we leave until the next call to OnCalculate()
      if(CopyRates(Symbol(), PERIOD_D1, day, 1, day_rate)==-1)
         return(prev_calculated);

      //---  get day start and end time
      datetime start_time=day_rate[0].time;
      datetime stop_time=start_time+PeriodSeconds(PERIOD_D1)-1;

      //--- get all intraday bars of the current day
      MqlRates bars_in_day[];
      if(CopyRates(Symbol(), PERIOD_CURRENT, start_time, stop_time, bars_in_day)==-1)
         return(prev_calculated);

      CMarketProfile *market_profile;
      //--- if the Market Profile has already been created and its drawing has been performed earlier
      if(prev_calculated>0)
        {
         //--- find the Market Profile object (CMarketProfile class) in the list by the opening time of the day with the 'day' index
         market_profile=GetMarketProfileByDate(ExtPrefixUniq, start_time);
         //--- if the object is not found, return zero to completely recalculate the indicator
         if(market_profile==NULL)
           {
            PrintFormat("Market Profile not found for %s. Indicator will be recalculated for all specified days",
                        TimeToString(start_time, TIME_DATE));
            return(0);
           }
         //--- CMarketProfile object is found in the list; set it to High and Low values of the day and pass the array of intraday bars
         //--- in this case, the object is shifted to a new coordinate corresponding to the High of the daily candle, and all arrays (vectors) are reinitialized
         market_profile.SetHiLoBars(day_rate[0].high, day_rate[0].low, bars_in_day);
        }
      //--- if this is the first calculation
      else
        {
         //--- create a new object of the CMarketProfile class to store the Market Profile of the day with 'day' index
         market_profile = new CMarketProfile(ExtPrefixUniq, start_time, stop_time, day_rate[0].high, day_rate[0].low, bars_in_day);
         //--- add a pointer to the created CMarketProfile object to the list
         mp_list.Add(market_profile);
        }
      //--- set canvas dimensions and line drawing parameters
      market_profile.UpdateSizes();
      //--- calculate profiles for each trading session
      market_profile.CalculateSessions();
      //--- draw the Market Profile
      market_profile.Draw(InpMultiplier);
     }
//--- redraw the chart after the loop has been completed and all objects have been created and updated
   ChartRedraw(0);

//--- return the number of bars for the next OnCalculate call
   return(rates_total);
  }

Die Logik der Timer ist in den Kommentaren zum Code ausführlich beschrieben. Kurz gesagt, ist sie wie folgt:

  • In einer Schleife nach der Anzahl der angezeigten Marktprofiltage;
    • den Tag in die Struktur aufnehmen, der dem Schleifenindex entspricht;
      • die Anzahl der Balken des aktuellen Chartzeitraums abrufen, die in dem in der Schleife ausgewählten Tag enthalten sind;
      • entweder ein zuvor erstelltes Marktprofilobjekt für den ausgewählten Tag abrufen oder ein neues erstellen, wenn es noch nicht in der Liste enthalten ist;
      • die Größe des Tagesbalkens von Tief bis Hoch in Chart-Pixeln ermitteln und die Arrays (Vektoren) der Handelssitzungen reinitialisieren;
    • entsprechend der neuen Größe des Balkens des ausgewählten Tages, ändern wir die Größe der Leinwand;
    • das Marktprofil des Tages für jede Sitzung neu zu berechnen;
    • die Profile der einzelnen Handelssitzungen auf der Leinwand neu zeichnen.
  • Am Ende der Schleife zeichnen wir das Chart neu.

In OnDeinit() des Indikators löschen wir alle erstellten grafischen Objekte:

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete all Market Profile graphical objects after use
   Print("Indicator \"Market Profile Canvas\" stopped, delete all objects CMarketProfile with prefix=", ExtPrefixUniq);

//--- in a loop by the number of CMarketProfile objects in the list
   int size=mp_list.Count();
   for(int i=0; i<size; i++)
     {
      //--- get the pointer to the CMarketProfile object from the list by the loop index
      CMarketProfile *market_profile;
      mp_list.TryGetValue(i, market_profile);
      //--- if the pointer is valid and the object exists, delete it
      if(market_profile!=NULL)
         if(CheckPointer(market_profile)!=POINTER_INVALID)
            delete market_profile;
     }
//--- redraw the chart to display the result immediately
   ChartRedraw(0);
  }

Wir ändern in der Ereignisbehandlung duch OnChartEvent() die Leinwandgröße für jeden Tag des Marktprofils:

//+------------------------------------------------------------------+
//| Custom indicator chart's event handler                           |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
  {
//--- if this is a user event, leave
   if(id>=CHARTEVENT_CUSTOM)
      return;

//--- if there is a chart change, update the sizes of all objects of the CMarketProfile class with redrawing the chart
   if(CHARTEVENT_CHART_CHANGE==id)
     {
      //--- in a loop by the number of CMarketProfile objects in the list
      int size=mp_list.Count();
      for(int i=0; i<size; i++)
        {
         //--- get the pointer to the CMarketProfile object by the loop index
         CMarketProfile *market_profile;
         mp_list.TryGetValue(i, market_profile);
         //--- if the object is received and if it is in the visible area of the chart
         if(market_profile)
            if(market_profile.isVisibleOnChart())
              {
               //--- update canvas dimensions and redraw market profile histograms
               market_profile.UpdateSizes();
               market_profile.Draw(InpMultiplier);
              }
        }
      //--- update the chart after recalculating all Profiles
      ChartRedraw();
     }
  }

Da die Skalierung der Chartanzeige vertikal und horizontal geändert werden kann, sollten grafische Objekte mit Handelssitzungs-Histogrammen auch in der Größe an die neuen Chartgrößen angepasst werden. Daher sollten in der Ereignisbehandlung Handler bei einer Änderung des Charts alle Objekte der Klasse CMarketProfile in der Größe aktualisiert und auf der Leinwand neu gezeichnet werden, die eine neue Größe entsprechend dem neuen Maßstab des Charts erhalten hat.

Die Funktion, die ein Marktprofilobjekt zurückgibt, das für eine bestimmte Tagesstartzeit erstellt wurde:

//+------------------------------------------------------------------+
//| Returns CMarketProfile or NULL by the date                       |
//+------------------------------------------------------------------+
CMarketProfile* GetMarketProfileByDate(string prefix, datetime time)
  {
//--- in a loop by the number of CMarketProfile objects in the list
   int size=mp_list.Count();
   for(int i=0; i<size; i++)
     {
      //--- get the pointer to the CMarketProfile object by the loop index
      CMarketProfile *market_profile;
      mp_list.TryGetValue(i, market_profile);
      //--- if the pointer is valid and the object exists,
      if(market_profile!=NULL)
         if(CheckPointer(market_profile)!=POINTER_INVALID)
           {
            //--- if the Market Profile object obtained by the pointer was created for the required time, return the pointer
            if(market_profile.Check(prefix, time))
               return(market_profile);
           }
     }
//--- nothing found - return NULL
   return(NULL);
  }

Die Funktion wird in der Indikatorschleife nach Handelstagen verwendet und liefert einen Zeiger auf das Objekt der Klasse CMarketProfile aus der Liste, die für einen Tagesbalken mit einer bestimmten Tageseröffnungszeit erstellt wurde. Die Funktion ermöglicht es uns, das gewünschte Objekt rechtzeitig zu erhalten, um es weiter zu aktualisieren.

Schlussfolgerung

Wir haben die Möglichkeit in Betracht gezogen, den Indikatorcode zu optimieren, um den Ressourcenverbrauch zu verringern. Wir haben Tausende von grafischen Objekten entfernt und sie durch ein einziges grafisches Objekt für einen einzigen Tag ersetzt, für den das Marktprofil gerendert wird.

Als Ergebnis der Optimierung wird jeder Handelstag in der in den Einstellungen angegebenen Anzahl (standardmäßig 7) auf einer eigenen Leinwand (OBJ_BITMAP-Objekt) angezeigt, auf der drei Handelssitzungen in Form von Histogrammen dargestellt werden - asiatisch, europäisch und amerikanisch, jede in ihrer eigenen, in den Einstellungen angegebenen Farbe. Drei Handelstage lang wird das Marktprofil letztlich so aussehen:

Hier haben wir nur drei grafische Objekte, auf denen die Histogramme der Handelssitzungen mit Hilfe der Klasse CCanvas gezeichnet werden. Es ist deutlich zu sehen, dass das erneute Rendern von sogar drei Bitmap-Grafikobjekten im laufenden Betrieb zu einem deutlichen Flackern und Zucken der Bilder führt. Dies deutet darauf hin, dass der Code noch weiter optimiert werden kann. Auf jeden Fall haben wir jetzt statt mehrerer tausend grafischer Objekte nur noch drei. Dies führt zu einer spürbaren Verbesserung des Ressourcenverbrauchs. Visuelle Artefakte können durch eine weitere Analyse des Codes korrigiert werden (denken Sie z. B. an die nicht implementierte Methode isChartScaleChanged() der Klasse CMarketProfile, die es uns ermöglicht, das Chart nur dann neu zu zeichnen, wenn sich der Maßstab tatsächlich ändert).

Zusammenfassend können wir mit Sicherheit sagen, dass jeder Code immer optimiert werden kann. Auch wenn dies den Rückgriff auf ein anderes Konzept für die Konstruktion der visuellen Komponente erfordern könnte, wie es in diesem Indikator geschieht.

Der Artikel enthält eine vollständig kommentierte Indikatordatei, die Sie herunterladen und selbst studieren und, wenn Sie es wünschen, weiter optimieren können.

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

Beigefügte Dateien |
Letzte Kommentare | Zur Diskussion im Händlerforum (5)
__zeus__
__zeus__ | 8 Jan. 2025 in 06:32
Warum nicht das perfekte Bandprofil schreiben
Ihor Herasko
Ihor Herasko | 8 Jan. 2025 in 08:38
__zeus__ #:
Warum nicht ein perfektes Bandprofil schreiben?

Was ist mit "perfekt" gemeint?

Artyom Trishkin
Artyom Trishkin | 8 Jan. 2025 in 10:58
__zeus__ #:
Warum nicht das perfekte Volumenprofil schreiben
Ich unterstütze Igor in seiner Frage
Nikolay Kuznetsov
Nikolay Kuznetsov | 6 Sept. 2025 in 08:18

Das ist ein interessantes Thema. Ich bin auch auf Canvas umgestiegen, um mein Profil anzuzeigen. Aber diese Methode hat einige Nachteile. Wenn grafische Figuren "gezeichnet und vergessen" werden, skaliert mt5 sie selbst, wenn man den Chart bewegt, die TF ändert, usw. Aktionen, dann sollte Canvas immer neu gezeichnet werden, bei jedem CHARTEVENT_CHART_CHANGE, auch wenn VP irgendwo in der Geschichte ist, auf bereits unveränderten Daten. Und diese Daten müssen beschafft werden und nicht immer sind sie (Tickdaten) im mt5-Cache, hier entweder den Speicher speichern und prüfen, ob sich die VP-Grenzen geändert haben oder ständig von mt5 anfordern (jetzt habe ich es so implementiert), was nicht schnell funktioniert und so werden die Daten nicht immer vollständig gegeben, und nicht sofort sicher.

Das Zeichnen ist ein Teil des Problems. Der zweite Teil ist, dass ein Trader nicht immer nur ein Bild braucht, um es zu bewundern, er braucht Signale, die auf diesem Bild basieren, wie POC, VHL, VLL, "entry into the void", etc. Und wir können Ereignisse nur an grafische Objekte binden (bedingt, wir können sie in einem Array irgendwo im Indikator aufbewahren, aber es ist immer noch wünschenswert zu visualisieren, wo dieses Niveau ist, wo der Alarm ausgelöst wurde). Das heißt, wir brauchen immer noch grafische Objekte für die Kanvas-VP, obwohl sie natürlich um eine oder sogar mehrere Größenordnungen kleiner sind als bei der Darstellung der VP durch grafische Objekte.

Generell lässt sich zusammenfassen, dass wir (noch?) kein volumetrisches Terminal von mt5 bekommen können (und wir brauchen Cluster, Deltas usw.).

Das Bild ist ein Beispiel dafür, wie VP auf einer Impulswelle aussieht, POC wird durch eine Linie dargestellt, hier sind Tick-Volumina, wenn es echte Volumina gäbe, würde ich mit ihnen arbeiten (finam, amp global). Wie die Praxis des Vergleichs von Tick-Volumen und realen Volumina bei älteren TFs gezeigt hat, stimmen sie entweder überein oder liegen sehr nahe beieinander, außer in Fällen, in denen große Volumina durch die Hoch- oder Tiefkerzen gehen; hier sind die Tick-Volumina machtlos, uns zu helfen.



BeeXXI Corporation
Nikolai Semko | 8 Sept. 2025 in 22:07
Nikolay Kuznetsov #:

Das ist ein interessantes Thema. Ich bin auch auf Canvas umgestiegen, um mein Profil anzuzeigen. Aber diese Methode hat einige Nachteile. Wenn grafische Figuren "gezeichnet und vergessen" werden, skaliert mt5 sie selbst, wenn man den Chart bewegt, die TF ändert, usw. Aktionen, dann sollte Canvas immer neu gezeichnet werden, bei jedem CHARTEVENT_CHART_CHANGE, auch wenn VP irgendwo in der Geschichte ist, auf bereits unveränderten Daten. Und diese Daten müssen beschafft werden und nicht immer sind sie (Tickdaten) im mt5-Cache, hier entweder den Speicher speichern und prüfen, ob sich die VP-Grenzen geändert haben oder ständig von mt5 anfordern (jetzt habe ich es so implementiert), was nicht schnell funktioniert und die Daten nicht immer vollständig gegeben sind, und nicht sofort sicher.

Das Zeichnen ist ein Teil des Problems. Der zweite Teil ist, dass ein Trader nicht immer nur ein Bild braucht, um es zu bewundern, er braucht Signale, die auf diesem Bild basieren, wie POC, VHL, VLL, "entry into the void", etc. Und wir können Ereignisse nur an grafische Objekte binden (bedingt, wir können sie in einem Array irgendwo im Indikator aufbewahren, aber es ist immer noch wünschenswert zu visualisieren, wo dieses Niveau ist, wo der Alarm ausgelöst wurde). D.h. wir brauchen immer noch grafische Objekte für die Canvas-VP, obwohl sie natürlich um eine oder sogar mehrere Größenordnungen kleiner sein werden als bei der Darstellung der VP durch grafische Objekte.

Im Allgemeinen lautet die Zusammenfassung wie folgt: Wir können (noch?) kein volumetrisches Terminal von mt5 bekommen (und wir brauchen Cluster, Deltas, etc.).

Das Bild ist ein Beispiel dafür, wie VP auf einer Impulswelle aussieht, POC wird durch eine Linie dargestellt, hier sind Tick-Volumina, wenn es echte Volumina gäbe, würde ich mit ihnen arbeiten (finam, amp global). Wie die Praxis des Vergleichs von Tick-Volumen und realen Volumina bei älteren TFs gezeigt hat, stimmen sie entweder überein oder liegen sehr nahe beieinander, außer in Fällen, in denen große Volumina durch die Hoch- oder Tiefkerzen gehen; hier sind die Tick-Volumina machtlos, uns zu helfen.



Das ist nicht ganz so. Dies sind keine Nachteile, sondern Eigenschaften, die Sie kennen müssen, um das Tool mit maximalen Möglichkeiten und Handlungsfreiheit zu nutzen.
Alles ist möglich, auch wenn es sich um 3D-Cluster handelt.
Hier ist ein MQL5-Beispiel , das nur Canvas verwendet, ohne OpenCL zu nutzen. Wenn Sie OpenCL (d. h. eine Grafikkarte) verwenden, können Sie eine erhebliche Beschleunigung (um das Dutzendfache) erreichen.

Neuronale Netze im Handel: Ein parameter-effizienter Transformer mit segmentierter Aufmerksamkeit (PSformer) Neuronale Netze im Handel: Ein parameter-effizienter Transformer mit segmentierter Aufmerksamkeit (PSformer)
In diesem Artikel wird das neue PSformer-Framework vorgestellt, das die Architektur des einfachen Transformers an die Lösung von Problemen im Zusammenhang mit multivariaten Zeitreihenprognosen anpasst. Der Rahmen basiert auf zwei wichtigen Innovationen: dem Parameter-Sharing-Mechanismus (PS) und der Segment Attention (SegAtt).
Neuronale Netze im Handel: Verbesserung des Wirkungsgrads der Transformer durch Verringerung der Schärfe (letzter Teil) Neuronale Netze im Handel: Verbesserung des Wirkungsgrads der Transformer durch Verringerung der Schärfe (letzter Teil)
SAMformer bietet eine Lösung für die wichtigsten Nachteile von Transformer-Modellen in der langfristigen Zeitreihenprognose, wie z. B. die Komplexität des Trainings und die schlechte Generalisierung auf kleinen Datensätzen. Die flache Architektur und die auf Schärfe ausgerichtete Optimierung helfen, suboptimale lokale Minima zu vermeiden. In diesem Artikel werden wir die Umsetzung von Ansätzen mit MQL5 fortsetzen und ihren praktischen Wert bewerten.
Erstellen von 3D-Balken auf der Grundlage von Zeit, Preis und Volumen Erstellen von 3D-Balken auf der Grundlage von Zeit, Preis und Volumen
Der Artikel befasst sich mit multivariaten Kurs-Charts in 3D und deren Erstellung. Wir werden auch untersuchen, wie 3D-Balken eine Preisumkehr vorhersagen, und wie Python und MetaTrader 5 es uns ermöglichen, diese Volumenbalken in Echtzeit darzustellen.
Zyklen im Handel Zyklen im Handel
In diesem Artikel geht es um die Verwendung von Zyklen im Handel. Wir werden den Aufbau einer Handelsstrategie auf der Grundlage zyklischer Modelle in Betracht ziehen.