English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
MQL5 Tarif Defteri MQL5'te Çok Sembollü Volatilite Göstergesi Geliştirme

MQL5 Tarif Defteri MQL5'te Çok Sembollü Volatilite Göstergesi Geliştirme

MetaTrader 5Örnekler | 13 Ocak 2022, 16:56
94 0
Anatoli Kazharski
Anatoli Kazharski

Giriş

Bu makalede, çok sembollü bir volatilite göstergesinin geliştirilmesini ele alacağız. Çok sembollü göstergelerin geliştirilmesi, acemi MQL5 geliştiricileri için bazı zorluklar ortaya çıkabilir, bu makale de bunun açıklığa kavuşturulmasına yardımcı olur. Çok sembollü bir göstergenin geliştirilmesi sırasında ortaya çıkan önemli sorunlar, diğer sembollerin verilerinin mevcut sembole göre senkronize edilmesi, bazı gösterge verilerinin olmaması ve belirli bir zaman aralığının "true" çubuklarının başlangıcının tanımlanması ile ilgilidir. Bu konuların tamamı makalede yakından ele alınacaktır.

İşleyiciye göre her sembol için önceden hesaplanmış Ortalama Gerçek Aralık (ATR) göstergesinin değerlerini elde edeceğiz. Örnekleme amacıyla, adları göstergenin harici parametrelerinde ayarlanabilen altı sembol olacak. Girilen adların doğruluğu kontrol edilecek. Parametrelerde belirtilen belirli bir sembol genel listede mevcut değilse, bunun için hesaplama yapılmayacaktır. Tüm mevcut semboller, burada halihazırda mevcut değilse Piyasa İzleme penceresine eklenecektir.

"MQL5 Tarif Defteri: Gösterge Alt Pencere Kontrolleri - Kaydırma Çubuğu" makalesinde, metni yazdırabileceğiniz ve hatta çizim yapabileceğiniz tuval hakkında konuşmuştuk. Bu sefer, tuval üzerine çizim yapmayacağız, bunun yerine bunu, kullanıcının belirli bir zamanda neler olduğunu bilmesini sağlayan mevcut program süreçleri hakkında mesajlar görüntülemek için kullanacağız.

 

Gösterge Geliştirme

Hadi programın geliştirmeye başlayalım. MQL5 Sihirbazını kullanarak, özel bir gösterge şablonu oluşturun. Birkaç değişiklikten sonra, aşağıda gösterilen kaynak kodunu elde etmeniz gerekir:

//+------------------------------------------------------------------+
//|                                               MultiSymbolATR.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
//--- Indicator properties
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window // Indicator is in a separate subwindow
#property indicator_minimum 0       // Minimum value of the indicator
#property indicator_buffers 6       // Number of buffers for indicator calculation
#property indicator_plots   6       // Number of plotting series
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialization completed successfully
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int      rates_total,     // Size of input time series
                const int      prev_calculated, // Bars processed at the previous call
                const datetime &time[],         // Opening time
                const double   &open[],         // Open prices
                const double   &high[],         // High prices
                const double   &low[],          // Low prices
                const double   &close[],        // Close prices
                const long     &tick_volume[],  // Tick volumes
                const long     &volume[],       // Real volumes
                const int      &spread[])       // Spread
  {
//--- Return the size of the data array of the current symbol
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
  }
//+------------------------------------------------------------------+

Fikrimizi uygulamak için, bu şablonu gereken şeyler ile daha fazla dolduracağız. Zamanlayıcı ihtiyacı makalenin devamında açıklanacaktır. Sabitleri başta, belirli gösterge özelliklerinden hemen sonra ekleyelim:

//--- Constants 
#define RESET           0 // Returning the indicator recalculation command to the terminal
#define LEVELS_COUNT    6 // Number of levels
#define SYMBOLS_COUNT   6 // Number of symbols

LEVELS_COUNT sabiti, "Yatay Çizgi" türünde (OBJ_HLINE) grafik nesneleri ile temsil edilen seviye sayısının değerini içerir. Bu seviyelerin değerleri göstergenin harici parametrelerinde belirtilebilir.

Projeye özel grafikleri ile çalışma sınıfına sahip bir dosya ekleyelim:

//--- Include the class for working with the canvas
#include <Canvas\Canvas.mqh>

Harici parametrelerde, iATR ortalama dönemi, volatilitesi görüntülenmesi gereken sembollerin adları ve yatay seviye değerlerini belirteceğiz. Semboller, ilk sembol göstergenin eklendiği grafiğe sahip sembol olarak değerlendirildiğinden, 2'den başlayarak numaralandırılmıştır.

//--- External parameters
input  int              IndicatorPeriod=14;       // Averaging period
sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - -
input  string           Symbol02       ="GBPUSD"; // Symbol 2
input  string           Symbol03       ="AUDUSD"; // Symbol 3
input  string           Symbol04       ="NZDUSD"; // Symbol 4
input  string           Symbol05       ="USDCAD"; // Symbol 5
input  string           Symbol06       ="USDCHF"; // Symbol 6
sinput string dlm02=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - -
input  int              Level01        =10;       // Level 1
input  int              Level02        =50;       // Level 2
input  int              Level03        =100;      // Level 3
input  int              Level04        =200;      // Level 4
input  int              Level05        =400;      // Level 5
input  int              Level06        =600;      // Level 6

Kodun devamında, daha sonra çalışmak için tüm global değişkenleri ve dizileri oluşturmalıyız. Bunların tamamı ayrıntılı yorumlar ile aşağıdaki kodda sağlanmaktadır:

//--- Global variables and arrays
CCanvas           canvas;                 // Loading the class
//--- Variables/arrays for copying data from OnCalculate()
int               OC_rates_total     =0;  // Size of input time series
int               OC_prev_calculated =0;  // Bars processed at the previous call
datetime          OC_time[];              // Opening time
double            OC_open[];              // Open prices
double            OC_high[];              // High prices
double            OC_low[];               // Low prices
double            OC_close[];             // Close prices
long              OC_tick_volume[];       // Tick volumes
long              OC_volume[];            // Real volumes
int               OC_spread[];            // Spread
//--- Structure of buffers for drawing indicator values
struct buffers {double data[];};
buffers           atr_buffers[SYMBOLS_COUNT];
//--- Structure of time arrays for data preparation
struct temp_time {datetime time[];};
temp_time         tmp_symbol_time[SYMBOLS_COUNT];
//--- Structure of arrays of the ATR indicator values for data preparation
struct temp_atr {double value[];};
temp_atr          tmp_atr_values[SYMBOLS_COUNT];
//--- For the purpose of storing and checking the time of the first bar in the terminal
datetime          series_first_date[SYMBOLS_COUNT];
datetime          series_first_date_last[SYMBOLS_COUNT];
//--- Time of the bar from which we will start drawing
datetime          limit_time[SYMBOLS_COUNT];
//--- Indicator levels
int               indicator_levels[LEVELS_COUNT];
//--- Symbol names
string            symbol_names[SYMBOLS_COUNT];
//--- Symbol handles
int               symbol_handles[SYMBOLS_COUNT];
//--- Colors of indicator lines
color             line_colors[SYMBOLS_COUNT]={clrRed,clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta};
//--- String representing the lack of the symbol
string            empty_symbol="EMPTY";
//--- Indicator subwindow properties
int               subwindow_number        =WRONG_VALUE;              // Subwindow number
int               chart_width             =0;                        // Chart width
int               subwindow_height        =0;                        // Subwindow height
int               last_chart_width        =0;                        // Last saved chart width
int               last_subwindow_height   =0;                        // Last saved subwindow height
int               subwindow_center_x      =0;                        // Horizontal center of the subwindow
int               subwindow_center_y      =0;                        // Vertical center of the subwindow
string            subwindow_shortname     ="MS_ATR";                 // Short name of the indicator
string            prefix                  =subwindow_shortname+"_";  // Prefix for objects
//--- Canvas properties
string            canvas_name             =prefix+"canvas";          // Canvas name
color             canvas_background       =clrBlack;                 // Canvas background color
uchar             canvas_opacity          =190;                      // Opacity
int               font_size               =16;                       // Font size
string            font_name               ="Calibri";                // Font
ENUM_COLOR_FORMAT clr_format              =COLOR_FORMAT_ARGB_RAW;    // Color components should be correctly set by the user
//--- Canvas messages
string            msg_invalid_handle      ="Invalid indicator handle! Please wait...";
string            msg_prepare_data        ="Preparing data! Please wait...";
string            msg_not_synchronized    ="Unsynchronized data! Please wait...";
string            msg_load_data           ="";
string            msg_sync_update         ="";
string            msg_last                ="";
//--- Maximum number of bars specified in the terminal settings
int               terminal_max_bars=0;

Göstergeyi grafiğe yüklerken, OnInit() fonksiyonu aşağıdaki eylemleri gerçekleştirecektir:

  • gösterge özelliklerinin ayarlanması;
  • çizim serileri çizme dizilerinin belirlenmesi;
  • dizilerin başlatılması;
  • harici parametrelerde belirtilen sembollerin Piyasa İzleme penceresine eklenmesi;
  • parametrelerin doğruluğunun kontrol edilmesi ve gösterge işleyicilerini elde etmek için ilk denemenin yapılması.

Tüm bu eylemler, ayrı fonksiyonlarda düzenlenirse daha uygun bir şekilde ele alınacaktır. Bunun sonucunda, OnInit() fonksiyonu kaynak kodunun anlaşılması aşağıda gösterildiği şekilde çok kolay hale gelecektir:

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Check input parameters for correctness
   if(!CheckInputParameters())
      return(INIT_PARAMETERS_INCORRECT);
//--- Set the timer at 1-second intervals
   EventSetTimer(1);
//--- Set the font to be displayed on the canvas
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Initialization of arrays
   InitArrays();
//--- Initialize the array of symbols 
   InitSymbolNames();
//--- Initialize the array of levels
   InitLevels();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Set indicator properties
   SetIndicatorProperties();
//--- Get the number of bars specified in the terminal settings
   terminal_max_bars=TerminalInfoInteger(TERMINAL_MAXBARS);
//--- Clear the comment
   Comment("");
//--- Refresh the chart
   ChartRedraw();
//--- Initialization completed successfully
   return(INIT_SUCCEEDED);
  }

Yukarıdaki kodda kullanılan özel fonksiyonlara daha yakından bakalım. CheckInputParameters() fonksiyonunda, harici parametrelerin doğruluğunu kontrol ederiz. Bizim durumumuzda, sadece bir parametreyi kontrol ederiz: ATR gösterge dönemi. Kısıtlama değerini 500olarak ayarladım. Yani, dönem değerini belirtilen değerden daha yüksek ayarlarsanız, gösterge çalışmayı durduracaktır ve programın sonlandırılma nedenine dair mesajı günlüğe ve grafik yorumuna yazdıracaktır. CheckInputParameters() fonksiyon kodu aşağıda verilmiştir.

//+------------------------------------------------------------------+
//| Checking input parameters for correctness                        |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
   if(IndicatorPeriod>500)
     {
      Comment("Decrease the indicator period! Indicator Period: ",IndicatorPeriod,"; Limit: 500;");
      printf("Decrease the indicator period! Indicator Period: %d; Limit: %d;",IndicatorPeriod,500);
      return(false);
     }
//---
   return(true);
  }
Bu arada, belirli bir fonksiyon tanımına hızlı bir şekilde geçmemiz gerekirse, imleci fonksiyon adının üzerine yerleştirmeniz ve içerik menüsünü çağırmak için fonksiyon üzerinde Alt+G tuşlarına basmanız veya sağ tıklamanız ve "Tanıma Git" seçeneğini seçmeniz gerekir. Fonksiyon başka bir dosyada tanımlanmışsa, bu dosya düzenleyicide açılacaktır. İçerik kitaplıklarını ve sınıfları da açabilirsiniz. Bu oldukça kullanışlıdır.

Daha sonra üç dizi başlatma fonksiyonuna geçiyoruz: InitArrays(), InitSymbolNames() ve InitLevels(). İlgili kaynak kodları aşağıda verilmektedir:

//+------------------------------------------------------------------+
//| First initialization of arrays                                   |
//+------------------------------------------------------------------+
void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);
   ArrayInitialize(symbol_handles,INVALID_HANDLE);
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
      ArrayInitialize(atr_buffers[s].data,EMPTY_VALUE);
  }
//+------------------------------------------------------------------+
//| Initializing array of symbols                                    |
//+------------------------------------------------------------------+
void InitSymbolNames()
  {
   symbol_names[0]=AddSymbolToMarketWatch(_Symbol);
   symbol_names[1]=AddSymbolToMarketWatch(Symbol02);
   symbol_names[2]=AddSymbolToMarketWatch(Symbol03);
   symbol_names[3]=AddSymbolToMarketWatch(Symbol04);
   symbol_names[4]=AddSymbolToMarketWatch(Symbol05);
   symbol_names[5]=AddSymbolToMarketWatch(Symbol06);
  }
//+------------------------------------------------------------------+
//| Initializing array of levels                                     |
//+------------------------------------------------------------------+
void InitLevels()
  {
   indicator_levels[0]=Level01;
   indicator_levels[1]=Level02;
   indicator_levels[2]=Level03;
   indicator_levels[3]=Level04;
   indicator_levels[4]=Level05;
   indicator_levels[5]=Level06;
  }

InitSymbolNames() fonksiyonunda, başka bir özel fonksiyonu -AddSymbolToMarketWatch()- kullanıyoruz. Bu, sembol adını alır ve bu sembol genel listede mevcutsa, Piyasa İzleme penceresine eklenir ve fonksiyon sembol adına sahip dizeyi döndürür. Bu sembol mevcut değilse, fonksiyon "BOŞ" dizesini döndürür ve diğer fonksiyonlarda kontroller çalıştırılırken semboller dizisinde bu öğe için başka hiçbir eylem gerçekleştirilmez.

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string AddSymbolToMarketWatch(string symbol)
  {
   int      total=0; // Number of symbols
   string   name=""; // Symbol name
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return(empty_symbol);
//--- Total symbols on the server
   total=SymbolsTotal(false);
//--- Iterate over the entire list of symbols
   for(int i=0;i<total;i++)
     {
      //--- Symbol name on the server
      name=SymbolName(i,false);
      //--- If this symbol is available,
      if(name==symbol)
        {
         //--- add it to the Market Watch window and
         SymbolSelect(name,true);
         //--- return its name
         return(name);
        }
     }
//--- If this symbol is not available, return the string representing the lack of the symbol
   return(empty_symbol);
  }

GetIndicatorHandles() gösterge başlatmada çağrılan başka bir fonksiyondur. Belirtilen her sembol için ATR gösterge işleyicilerini elde etmeye çalışır. İşleyici bir sembol için elde edilmemişse, fonksiyon false değerini döndürür, ancak işleyici kullanılabilirliği programın diğer kısımlarında kontrol edileceğinden bu, OnInit() içinde hiçbir şekilde işlenmeyecektir.

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
bool GetIndicatorHandles()
  {
//--- An indication of all handles being valid
   bool valid_handles=true;
//--- Iterate over all symbols in a loop and ...
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If the symbol is available
      if(symbol_names[s]!=empty_symbol)
        {
         // And if the handle of the current symbol is invalid
         if(symbol_handles[s]==INVALID_HANDLE)
           {
            //--- Get it
            symbol_handles[s]=iATR(symbol_names[s],Period(),IndicatorPeriod);
            //--- If the handle could not be obtained, try again next time
            if(symbol_handles[s]==INVALID_HANDLE)
               valid_handles=false;
           }
        }
     }
//--- Print the relevant message if the handle for one of the symbols could not be obtained
   if(!valid_handles)
     {
      msg_last=msg_invalid_handle;
      ShowCanvasMessage(msg_invalid_handle);
     }
//---
   return(valid_handles);
  }

ShowCanvasMessage() fonksiyonu, tuval ile çalışmaya yönelik diğer fonksiyonlar ile birlikte biraz daha sonra incelenecektir.

Gösterge özellikleri SetIndicatorProperties() fonksiyonunda ayarlanır. Her çizim serisinin özellikleri benzer olduğundan, bunları döngüler kullanarak ayarlamak daha uygundur:

//+------------------------------------------------------------------+
//| Setting indicator properties                                     |
//+------------------------------------------------------------------+
void SetIndicatorProperties()
  {
//--- Set the short name
   IndicatorSetString(INDICATOR_SHORTNAME,subwindow_shortname);
//--- Set the number of decimal places
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//--- Define buffers for drawing
   for(int s=0; s<SYMBOLS_COUNT; s++)
      SetIndexBuffer(s,atr_buffers[s].data,INDICATOR_DATA);
//--- Set labels for the current symbol
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetString(s,PLOT_LABEL,"ATR ("+IntegerToString(s)+", "+symbol_names[s]+")");
//--- Set the plotting type: lines
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
//--- Set the line width
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1);
//--- Set the line color
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
//--- Empty value for plotting where nothing will be drawn
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

Programın başarıyla başlatılmasının ardından, OnCalculate() fonksiyonunun ilk çağrısını yapmamız gerekir. prev_calculated değişkeninin değeri ilk fonksiyon çağrısında sıfırdır. Bu aynı zamanda, daha derin bir geçmiş yüklenirken veya tarihteki boşluklar doldurulurken terminal tarafından sıfırlanır. Bu tür durumlarda, gösterge tamponları bütünüyle yeniden hesaplanır. Bu parametre değeri sıfır dışındaysa, yani giriş zaman gerisinin boyutu olan aynı fonksiyon tarafından daha önce döndürülen sonuç, tamponların yalnızca son değerlerini güncelleştirmek yeterlidir.

İlk denemede tüm hesaplamaları doğru şekilde yapmayı başaramayabilirsiniz. Bu durumda, geri dönmek için, sıfır değerini içeren RESET sabitini kullanacağız. OnCalculate() fonksiyonunun sonraki çağrısında (örneğin sonraki tikte), prev_calculated parametresi sıfır değerini içerecektir; bu, grafikte göstergenin çizim serilerini görüntülemeden önce gerekli hesaplamaların tamamını bir kez daha yapmaya çalışacağımız anlamına gelir.

Ancak piyasa kapalı olduğunda ve yeni tikler olmadığında veya başarısız hesaplamaların ardından grafik boş kalacaktır. Bu durumda, grafik zaman aralığını manuel olarak değiştirerek başka bir deneme yapma komutu vermenin basit bir yolunu deneyebilirsiniz. Ancak biz farklı bir yaklaşım kullanacağız. Bunun için, en başta, program şablonumuza zamanlayıcıyı -OnTimer() fonksiyonu - ekledik ve OnInit() fonksiyonunda 1 saniyelik zaman aralığı ayarladık.

Her saniye, zamanlayıcı OnCalculate() fonksiyonunun sıfır değeri döndürüp döndürmediğini kontrol edecektir. Bunun için, OnCalculate() fonksiyonundan tüm parametreleri OC_ ön eki ile ilgili adlara ve dizilere sahip global değişkenlere kopyalayacak CopyDataOnCalculate() fonksiyonunu yazacağız.

//+------------------------------------------------------------------+
//| Copying data from OnCalculate                                    |
//+------------------------------------------------------------------+
void CopyDataOnCalculate(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[])
  {
   OC_rates_total=rates_total;
   OC_prev_calculated=prev_calculated;
   ArrayCopy(OC_time,time);
   ArrayCopy(OC_open,open);
   ArrayCopy(OC_high,high);
   ArrayCopy(OC_low,low);
   ArrayCopy(OC_close,close);
   ArrayCopy(OC_tick_volume,tick_volume);
   ArrayCopy(OC_volume,volume);
   ArrayCopy(OC_spread,spread);
  }

Bu fonksiyon, OnCalculate() fonksiyonu gövdesinin en başında çağrılmalıdır. Ayrıca, en başta, bunları gösterge tamponlarına yerleştirmeden önce boyutu veri hazırlama dizilerine ayarlayacak başka bir özel fonksiyon olan ResizeCalculatedArrays() fonksiyonunu eklemeliyiz. Bu dizilerin boyutu giriş zaman serisinin boyutuna eşit olmalıdır.

//+------------------------------------------------------------------+
//| Resizing the size of arrays to the size of the main array        |
//+------------------------------------------------------------------+
void ResizeCalculatedArrays()
  {
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      ArrayResize(tmp_symbol_time[s].time,OC_rates_total);
      ArrayResize(tmp_atr_values[s].value,OC_rates_total);
     }
  }

Ayrıca, veri hazırlama dizilerini, bunları grafiğe çıkarmadan önce sıfıra başlatan bir ZeroCalculatedArrays() fonksiyonu oluşturacağız.

//+------------------------------------------------------------------+
//| Zeroing out arrays for data preparation                          |
//+------------------------------------------------------------------+
void ZeroCalculatedArrays()
  {
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      ArrayInitialize(tmp_symbol_time[s].time,NULL);
      ArrayInitialize(tmp_atr_values[s].value,EMPTY_VALUE);
     }
  }

Aynı fonksiyon, gösterge tamponlarını ön sıfırlamak için de gerekli olacaktır. Buna ZeroIndicatorBuffers()diyelim.

//+------------------------------------------------------------------+
//| Zeroing out indicator buffers                                    |
//+------------------------------------------------------------------+
void ZeroIndicatorBuffers()
  {
   for(int s=0; s<SYMBOLS_COUNT; s++)
      ArrayInitialize(atr_buffers[s].data,EMPTY_VALUE);
  }

OnCalculate() fonksiyonunun mevcut kodu aşağıda gösterildiği gibi olacaktır. Daha sonra doldurulacak ana işlemler için yorumlar da sağladım (yorumlar ve altındaki noktalar).

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int      rates_total,     // Size of input time series
                const int      prev_calculated, // Bars processed at the previous call
                const datetime &time[],         // Opening time
                const double   &open[],         // Open prices
                const double   &high[],         // High prices
                const double   &low[],          // Low prices
                const double   &close[],        // Close prices
                const long     &tick_volume[],  // Tick volumes
                const long     &volume[],       // Real volumes
                const int      &spread[])       // Spread
  {
//--- For the purpose of determining the bar from which the calculation shall be made
   int limit=0;
//--- Make a copy of the OnCalculate() parameters
   CopyDataOnCalculate(rates_total,prev_calculated,
                       time,open,high,low,close,
                       tick_volume,volume,spread);
//--- Set the size to arrays for data preparation
   ResizeCalculatedArrays();
//--- If this is the first calculation or if a deeper history has been loaded or gaps in the history have been filled
   if(prev_calculated==0)
     {
      //--- Zero out arrays for data preparation
      ZeroCalculatedArrays();
      //--- Zero out indicator buffers
      ZeroIndicatorBuffers();
      //--- Other checks
      // ...
      //--- If you reached this point, it means that OnCalculate() will return non-zero value and this needs to be saved
      OC_prev_calculated=rates_total;
     }
//--- If only the last values need to be recalculated
   else
      limit=prev_calculated-1;

//--- Prepare data for drawing
// ...
//--- Fill arrays with data for drawing
// ...

//--- Return the size of the data array of the current symbol
   return(rates_total);
  }

Şu anda OnTimer() fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- If for some reason calculations have not been completed or
//    a deeper history has been loaded or
//    gaps in the history have been filled, 
//    then make another attempt without waiting for the new tick
   if(OC_prev_calculated==0)
     {
      OnCalculate(OC_rates_total,OC_prev_calculated,
                  OC_time,OC_open,OC_high,OC_low,OC_close,
                  OC_tick_volume,OC_volume,OC_spread);
     }
  }

Şimdi, prev_calculated değişkeni sıfıra eşit olduğunda kullanılacak diğer fonksiyonları değerlendirelim. Bu fonksiyonlar şunları yapacaktır:

  • gerekli miktarda veri (çubuk) yüklemek ve oluşturmak;
  • tüm işleyicilerin kullanılabilirliğini kontrol etmek;
  • gerekli miktarda verinin hazır olup olmadığını kontrol etmek;
  • verileri sunucu ile eşitlemek;
  • çizim serisinin çizileceği çubukları belirlemek.

Ek olarak, her sembol için ilk "true" çubuğunu tanımlayacağız. Bu kısa terim, bunu daha sonra bağlama daha uygun hale getirmek için türetilmiştir. Anlamı şudur. MetaTrader 5'teki tüm zaman aralıkları dakika verilerinden oluşturulur. Ancak, örneğin sunucudaki günlük veriler 1993'ten itibaren kullanılabilirken, dakika verileri yalnızca 2000'den itibaren kullanılabilirse, bu durumda örneğin saatlik grafik zaman aralığını seçersek, çubuklar dakika verilerinin kullanılabilir olduğu tarihten başlayarak, yani 2000 yılından itibaren, oluşturulacaktır. 2000 öncesindeki her şey, günlük veriler veya mevcut zaman aralığına en yakın veriler ile temsil edilecektir. Dolayısıyla, kafanız karışmasın diye, gösterge verilerini mevcut zaman aralığı ile ilgili olmayan veriler için görüntülememelisiniz. İşte bu yüzden, mevcut zaman aralığının ilk "true" çubuğunu tanımlayacağız ve bunu, sembolün gösterge tamponuyla aynı renkte dikey bir çizgiyle işaretleyeceğiz.

Parametreler belirli bir zaman aralığı için optimize edilirse, diğer zaman dilimlerindeki verilerin bu durumda uygunsuz olacağından, "true" çubuklarının tanımlanması Uzman Danışmanlar geliştirilirken de önemlidir.

Yukarıdaki kontrolleri yapmadan önce, tuvali gösterge alt penceresine ekleyeceğiz. Bu yüzden, ilk olarak tuvali yönetmek için ihtiyacımız olacak tüm fonksiyonları yazmalıyız. Tuvali alt pencereye eklemeden önce, tuvalde hangi metin mesajlarının görüntüleneceğine dayalı olarak bunun boyutunu ve koordinatlarını belirlememiz gerekir. Bunun için getsubwindowGeometry() fonksiyonunu yazalım:

//+------------------------------------------------------------------+
//| Getting geometry of the indicator subwindow                      |
//+------------------------------------------------------------------+
void GetSubwindowGeometry()
  {
//--- Get the indicator subwindow number
   subwindow_number=ChartWindowFind(0,subwindow_shortname);
//--- Get the subwindow width and height
   chart_width=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
   subwindow_height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,subwindow_number);
//--- Calculate the center of the subwindow
   subwindow_center_x=chart_width/2;
   subwindow_center_y=subwindow_height/2;
  }

Alt pencere özellikleri elde edildiğinde, tuvali ekleyebilirsiniz. Bunun arka planı %100 şeffaf (opaklık 0'a eşit) olacaktır ve kullanıcıya o anda ne olduğunu göstermek için yalnızca veri yüklenirken veya oluşturulurken görünür olacaktır. Görünür olduğunda arka plan opaklığı 190'a eşit olacaktır. Opaklık değerini 0 ve 255 arasında herhangi bir değere ayarlayabilirsiniz. Daha fazla bilgi için lütfen Yardım kısmından erişilebilir ColorToARGB() fonksiyonu açıklamasına bakın.

Tuvali ayarlamak için bir SetCanvas() fonksiyonu yazalım:

//+------------------------------------------------------------------+
//| Setting canvas                                                   |
//+------------------------------------------------------------------+
void SetCanvas()
  {
//--- If there is no canvas, set it
   if(ObjectFind(0,canvas_name)<0)
     {
      //--- Create the canvas
      canvas.CreateBitmapLabel(0,subwindow_number,canvas_name,0,0,chart_width,subwindow_height,clr_format);
      //--- Make the canvas completely transparent
      canvas.Erase(ColorToARGB(canvas_background,0));
      //--- Redraw the canvas
      canvas.Update();
     }
  }

Ayrıca, gösterge alt penceresinin yeniden boyutlandırılıp boyutlandırılmadığını kontrol eden bir fonksiyona ihtiyacımız olacaktır. Boyutlandırılmışsa, tuval boyutu otomatik olarak yeni alt pencere boyutuna ayarlanacaktır. Bu fonksiyona OnSubwindowChange() diyelim:

//+------------------------------------------------------------------+
//| Checking the subwindow size                                      |
//+------------------------------------------------------------------+
void OnSubwindowChange()
  {
//--- Get subwindow properties
   GetSubwindowGeometry();
//--- If the subwindow size has not changed, exit
   if(!SubwindowSizeChanged())
      return;
//--- If the subwindow height is less than one pixel or if the center has been calculated incorrectly, exit
   if(subwindow_height<1 || subwindow_center_y<1)
      return;
//--- Set the new canvas size
   ResizeCanvas();
//--- Show the last message
   ShowCanvasMessage(msg_last);
  }

Yukarıdaki kodda vurgulanan fonksiyonlar aşağıda incelenebilir. Lütfen alt pencereyi yeniden boyutlandırmadan önce çalıştırılan kontrol türlerine dikkat edin. Herhangi bir özellik yanlış çıkarsa, fonksiyon çalışmayı durdurur.

SubwindowSizeChanged() fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Checking if the subwindow has been resized                       |
//+------------------------------------------------------------------+
bool SubwindowSizeChanged()
  {
//--- If the subwindow size has not changed, exit
   if(last_chart_width==chart_width && last_subwindow_height==subwindow_height)
      return(false);
//--- If the size has changed, save it
   else
     {
      last_chart_width=chart_width;
      last_subwindow_height=subwindow_height;
     }
//---
   return(true);
  }

ResizeCanvas() fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Resizing canvas                                                  |
//+------------------------------------------------------------------+
void ResizeCanvas()
  {
//--- If the canvas has already been added to the indicator subwindow, set the new size
   if(ObjectFind(0,canvas_name)==subwindow_number)
      canvas.Resize(chart_width,subwindow_height);
  }

Ve son olarak, aşağıda gösterge işleyicilerini elde ederken daha önce kullanılan ShowCanvasMessage() fonksiyon kodu yer almaktadır:

//+------------------------------------------------------------------+
//| Displaying message on the canvas                                 |
//+------------------------------------------------------------------+
void ShowCanvasMessage(string message_text)
  {
   GetSubwindowGeometry();
//--- If the canvas has already been added to the indicator subwindow
   if(ObjectFind(0,canvas_name)==subwindow_number)
     {
      //--- If the string passed is not empty and correct coordinates have been obtained, display the message
      if(message_text!="" && subwindow_center_x>0 && subwindow_center_y>0)
        {
         canvas.Erase(ColorToARGB(canvas_background,canvas_opacity));
         canvas.TextOut(subwindow_center_x,subwindow_center_y,message_text,ColorToARGB(clrRed),TA_CENTER|TA_VCENTER);
         canvas.Update();
        }
     }
  }

Tuval, kaybolma efekti ile silinecektir. Bunu uygulamak için, tuvali silmeden önce, tuvali her yinelemede yenilerken, opaklığı bir döngüde kademeli olarak geçerli değerden sıfıra değiştirmemiz gerekir.

DeleteCanvas() fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Deleting canvas                                                  |
//+------------------------------------------------------------------+
void DeleteCanvas()
  {
//--- Delete the canvas if it exists
   if(ObjectFind(0,canvas_name)>0)
     {
      //--- Before deleting, implement the disappearing effect
      for(int i=canvas_opacity; i>0; i-=5)
        {
         canvas.Erase(ColorToARGB(canvas_background,(uchar)i));
         canvas.Update();
        }
      //--- Delete the graphical resource
      canvas.Destroy();
     }
  }

Daha sonra, bunları gösterge tamponlarına yerleştirmeden ve grafikte görüntülemeden önce verilerin hazır olup olmadığını kontrol etmek için gereken fonksiyonlara göz atalım. LoadAndFormData() fonksiyonu ile başlayalım. Bunu, mevcut sembol dizisinin boyutunu diğer semboller için kullanılabilir veriler ile karşılaştırmak için kullanırız. Veriler, gerekirse, sunucudan yüklenir. Fonksiyon kodu, değerlendirmeniz için ayrıntılı yorumlarla birlikte sağlanır.

//+------------------------------------------------------------------+
//| Loading and generating the necessary/available amount of data    |
//+------------------------------------------------------------------+
void LoadAndFormData()
  {
   int bars_count=100; // Number of loaded bars
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      int      attempts          =0;    // Counter of data copying attempts
      int      array_size        =0;    // Array size
      datetime firstdate_server  =NULL; // Time of the first bar on the server
      datetime firstdate_terminal=NULL; // Time of the first bar in the terminal base
      //--- Get the first date by the symbol/time frame in the terminal base
      SeriesInfoInteger(symbol_names[s],Period(),SERIES_FIRSTDATE,firstdate_terminal);
      //--- Get the first date of the symbol/time frame on the server
      SeriesInfoInteger(symbol_names[s],Period(),SERIES_SERVER_FIRSTDATE,firstdate_server);
      //--- Print the message
      msg_last=msg_load_data="Loading and generating data: "+
               symbol_names[s]+"("+(string)(s+1)+"/"+(string)SYMBOLS_COUNT+") ... ";
      ShowCanvasMessage(msg_load_data);
      //--- Load/generate data.
      //    If the array size is smaller than the maximum number of bars in the terminal, and if
      //    the number of bars between the first date of the series in the terminal and the first date of the series on the server is more than specified
      while(array_size<OC_rates_total && 
            firstdate_terminal-firstdate_server>PeriodSeconds()*bars_count)
        {
         datetime copied_time[];
         //--- Get the first date by the symbol/time frame in the terminal base
         SeriesInfoInteger(symbol_names[s],Period(),SERIES_FIRSTDATE,firstdate_terminal);
         //--- Load/copy the specified number of bars
         if(CopyTime(symbol_names[s],Period(),0,array_size+bars_count,copied_time)!=-1)
           {
            //--- If the time of the first bar in the array, excluding the number of the bars being loaded, is earlier 
            //    than the time of the first bar in the chart, terminate the loop
            if(copied_time[0]-PeriodSeconds()*bars_count<OC_time[0])
               break;
            //--- If the array size hasn't increased, increase the counter
            if(ArraySize(copied_time)==array_size)
               attempts++;
            //--- Otherwise get the current size of the array
            else
               array_size=ArraySize(copied_time);
            //--- If the array size hasn't increased over 100 attempts, terminate the loop
            if(attempts==100)
              {
               attempts=0;
               break;
              }
           }
         //--- Check the subwindow size once every 2000 bars 
         //    and if the size has changed, adjust the canvas size to it
         if(!(array_size%2000))
            OnSubwindowChange();
        }
     }
  }

Gerekli miktarda veriyi yükleme girişiminden sonra, bir kez daha gösterge işleyicilerini kontrol ediyoruz. Bunun için, yukarıda değerlendirilen GetIndicatorHandles() fonksiyonunu kullanıyoruz.

İşleyiciler kontrol edildikten sonra, program CheckAvailableData() fonksiyonunu kullanarak belirtilen sembollerin verileri ve her bir sembol için gösterge değerlerinin kullanılabilirliğini kontrol eder. Aşağıda bunun nasıl yapıldığını daha yakından görebilirsiniz:

//+------------------------------------------------------------------+
//| Checking the amount of available data for all symbols            |
//+------------------------------------------------------------------+
bool CheckAvailableData()
  {
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If this symbol is available
      if(symbol_names[s]!=empty_symbol)
        {
         double   data[]; // Array for checking the amount of indicator data
         datetime time[]; // Array for checking the number of bars
         int      calculated_values =0;    // Amount of indicator data
         int      available_bars    =0;    // Number of bars of the current period
         datetime firstdate_terminal=NULL; // First date of the current time frame data available in the terminal
         //--- Get the number of calculated values of the indicator
         calculated_values=BarsCalculated(symbol_handles[s]);
         //--- Get the first date of the current time frame data in the terminal
         firstdate_terminal=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE);
         //--- Get the number of available bars from the date specified
         available_bars=Bars(symbol_names[s],Period(),firstdate_terminal,TimeCurrent());
         //--- Check the readiness of bar data: 5 attempts to get values
         for(int i=0; i<5; i++)
           {
            //--- Copy the specified amount of data
            if(CopyTime(symbol_names[s],Period(),0,available_bars,time)!=-1)
              {
               //--- If the required amount has been copied, terminate the loop
               if(ArraySize(time)>=available_bars)
                  break;
              }
           }
         //--- Check the readiness of indicator data: 5 attempts to get values
         for(int i=0; i<5; i++)
           {
            //--- Copy the specified amount of data
            if(CopyBuffer(symbol_handles[s],0,0,calculated_values,data)!=-1)
              {
               //--- If the required amount has been copied, terminate the loop
               if(ArraySize(data)>=calculated_values)
                  break;
              }
           }
         //--- If the amount of data copied is not sufficient, one more attempt is required
         if(ArraySize(time)<available_bars || ArraySize(data)<calculated_values)
           {
            msg_last=msg_prepare_data;
            ShowCanvasMessage(msg_prepare_data);
            OC_prev_calculated=0;
            return(false);
           }
        }
     }
//---
   return(true);
  }

CheckAvailableData() fonksiyonu, tüm semboller için veriler hazır oluncaya kadar daha fazla hesaplama yapılmasına izin vermeyecektir. Tüm kontrol fonksiyonlarının çalışması da benzer bir model izler.

Sonraki fonksiyon, kotasyonların daha derin bir geçmişini yükleme olayını görüntülemek için gereklidir. Buna CheckEventLoadHistory() diyelim. Daha büyük miktarda veri yüklenirse, gösterge tümüyle yeniden hesaplanmalıdır. Bu fonksiyonun kaynak kodu aşağıda verilmektedir:

//+------------------------------------------------------------------+
//| Checking the event of loading a deeper history                   |
//+------------------------------------------------------------------+
bool CheckLoadedHistory()
  {
   bool loaded=false;
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If this symbol is available
      if(symbol_names[s]!=empty_symbol)
        {
         //--- If the series need to be updated
         if(OC_prev_calculated==0)
           {
            //--- Get the first date by the symbol/time frame
            series_first_date[s]=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_FIRSTDATE);
            //--- If this is the first time (no value is available), then
            if(series_first_date_last[s]==NULL)
               //--- Store the first date by the symbol/time frame for further comparison 
               //    in order to determine if a deeper history has been loaded
               series_first_date_last[s]=series_first_date[s];
           }
         else
           {
            //--- Get the first date by the symbol/time frame
            series_first_date[s]=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_FIRSTDATE);
            //--- If the dates are different, i.e. the date in the memory is later than the one we have just obtained,
            //     this means that a deeper history has been loaded
            if(series_first_date_last[s]>series_first_date[s])
              {
               //--- Print the relevant message to the log
               Print("(",symbol_names[s],",",TimeframeToString(Period()),
                     ") > A deeper history has been loaded/generated: ",
                     series_first_date_last[s]," > ",series_first_date[s]);
               //--- Store the date
               series_first_date_last[s]=series_first_date[s];
               loaded=true;
              }
           }
        }
     }
//--- If a deeper history has been loaded/generated, then
//    send the command to refresh the plotting series of the indicator
   if(loaded)
      return(false);
//---
   return(true);
  }

Terminaldeki ve sunucudaki veriler arasındaki senkronizasyonu kontrol etmek için başka bir fonksiyon yazalım. Bu kontrol, sadece sunucu bağlantısı kurulursa çalıştırılır. CheckSymbolIsSynchronized() fonksiyon kodu aşağıda verilmektedir:

//+------------------------------------------------------------------+
//| Checking synchronization by symbol/time frame                    |
//+------------------------------------------------------------------+
bool CheckSymbolIsSynchronized()
  {
//--- If the connection to the server is established, check the data synchronization
   if(TerminalInfoInteger(TERMINAL_CONNECTED))
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         //--- If the symbol is available
         if(symbol_names[s]!=empty_symbol)
           {
            //--- If the data are not synchronized, print the relevant message and try again
            if(!SeriesInfoInteger(symbol_names[s],Period(),SERIES_SYNCHRONIZED))
              {
               msg_last=msg_not_synchronized;
               ShowCanvasMessage(msg_not_synchronized);
               return(false);
              }
           }
        }
     }
//---
   return(true);
  }

Zaman aralığını bir dizeye dönüştürme fonksiyonu "MQL5 Tarif Defteri" serisinin önceki makalelerinden alınacaktır:

//+------------------------------------------------------------------+
//| Converting time frame to a string                                |
//+------------------------------------------------------------------+
string TimeframeToString(ENUM_TIMEFRAMES timeframe)
  {
   string str="";
//--- If the value passed is incorrect, take the current chart time frame
   if(timeframe==WRONG_VALUE || timeframe== NULL)
      timeframe= Period();
   switch(timeframe)
     {
      case PERIOD_M1  : str="M1";  break;
      case PERIOD_M2  : str="M2";  break;
      case PERIOD_M3  : str="M3";  break;
      case PERIOD_M4  : str="M4";  break;
      case PERIOD_M5  : str="M5";  break;
      case PERIOD_M6  : str="M6";  break;
      case PERIOD_M10 : str="M10"; break;
      case PERIOD_M12 : str="M12"; break;
      case PERIOD_M15 : str="M15"; break;
      case PERIOD_M20 : str="M20"; break;
      case PERIOD_M30 : str="M30"; break;
      case PERIOD_H1  : str="H1";  break;
      case PERIOD_H2  : str="H2";  break;
      case PERIOD_H3  : str="H3";  break;
      case PERIOD_H4  : str="H4";  break;
      case PERIOD_H6  : str="H6";  break;
      case PERIOD_H8  : str="H8";  break;
      case PERIOD_H12 : str="H12"; break;
      case PERIOD_D1  : str="D1";  break;
      case PERIOD_W1  : str="W1";  break;
      case PERIOD_MN1 : str="MN1"; break;
     }
//---
   return(str);
  }

Ve son olarak, her sembol için ilk true çubuğu, bunu grafikte dikey bir çizgi ile işaretleyerek tanımlamamız ve kaydetmemiz gerekir. Bunu yapmak için, bir DetermineFirstTrueBar() fonksiyonu ve ilk true çubuğun zamanını döndüren bir yardımcı GetFirstTrueBarTime() fonksiyonunu yazalım.

//+-----------------------------------------------------------------------+
//| Determining the time of the first true bar for the purpose of drawing |
//+-----------------------------------------------------------------------+
bool DetermineFirstTrueBar()
  {
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      datetime time[];           // Bar time array
      int      available_bars=0; // Number of bars
      //--- If this symbol is not available, move to the next one
      if(symbol_names[s]==empty_symbol)
         continue;
      //--- Get the total number of bars for the symbol
      available_bars=Bars(symbol_names[s],Period());
      //--- Copy the bar time array. If this action failed, try again.
      if(CopyTime(symbol_names[s],Period(),0,available_bars,time)<available_bars)
         return(false);
      //--- Get the time of the first true bar corresponding to the current time frame
      limit_time[s]=GetFirstTrueBarTime(time);
      //--- Place a vertical line on the true bar
      CreateVerticalLine(0,0,limit_time[s],prefix+symbol_names[s]+": begin time series",
                          2,STYLE_SOLID,line_colors[s],false,TimeToString(limit_time[s]),"\n");
     }
//---
   return(true);
  }
//+-----------------------------------------------------------------------+
//| Returning the time of the first true bar of the current time frame    |
//+-----------------------------------------------------------------------+
datetime GetFirstTrueBarTime(datetime &time[])
  {
   datetime true_period =NULL; // Time of the first true bar
   int      array_size  =0;    // Array size
//--- Get the array size
   array_size=ArraySize(time);
   ArraySetAsSeries(time,false);
//--- Check each bar one by one
   for(int i=1; i<array_size; i++)
     {
      //--- If the bar corresponds to the current time frame
      if(time[i]-time[i-1]==PeriodSeconds())
        {
         //--- Save it and terminate the loop
         true_period=time[i];
         break;
        }
     }
//--- Return the time of the first true bar
   return(true_period);
  }

İlk true çubuğun zamanı, CreateVerticalLine() fonksiyonu kullanılarak grafikte bir dikey çizgi ile işaretlenir:

//+------------------------------------------------------------------+
//| Creating a vertical line at the specified time point             |
//+------------------------------------------------------------------+
void CreateVerticalLine(long            chart_id,           // chart id
                        int             window_number,      // window number
                        datetime        time,               // time
                        string          object_name,        // object name
                        int             line_width,         // line width
                        ENUM_LINE_STYLE line_style,         // line style
                        color           line_color,         // line color
                        bool            selectable,         // cannot select the object if FALSE
                        string          description_text,   // text of the description
                        string          tooltip)            // no tooltip if "\n"
  {
//--- If the object has been created successfully
   if(ObjectCreate(chart_id,object_name,OBJ_VLINE,window_number,time,0))
     {
      //--- set its properties
      ObjectSetInteger(chart_id,object_name,OBJPROP_TIME,time);
      ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,selectable);
      ObjectSetInteger(chart_id,object_name,OBJPROP_STYLE,line_style);
      ObjectSetInteger(chart_id,object_name,OBJPROP_WIDTH,line_width);
      ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,line_color);
      ObjectSetString(chart_id,object_name,OBJPROP_TEXT,description_text);
      ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,tooltip);
     }
  }

Kontrol fonksiyonları hazırdır. Bunun sonucunda, OnCalculate() fonksiyon kodunun, prev_calculated değişkeni sıfıra eşit olduğundaki kısmı artık aşağıda gösterildiği gibi görünecektir:

//--- If this is the first calculation or if a deeper history has been loaded or gaps in the history have been filled
   if(prev_calculated==0)
     {
      //--- Zero out arrays for data preparation
      ZeroCalculatedArrays();
      //--- Zero out indicator buffers
      ZeroIndicatorBuffers();
      //--- Get subwindow properties
      GetSubwindowGeometry();
      //--- Add the canvas
      SetCanvas();
      //--- Load and generate the necessary/available amount of data
      LoadAndFormData();
      //--- If there is an invalid handle, try to get it again
      if(!GetIndicatorHandles())
         return(RESET);
      //--- Check the amount of data available for all symbols
      if(!CheckAvailableData())
         return(RESET);
      //--- Check if a deeper history has been loaded
      if(!CheckLoadedHistory())
         return(RESET);
      //--- Check synchronization by symbol/time frame at the current moment
      if(!CheckSymbolIsSynchronized())
         return(RESET);
      //--- For each symbol, determine the bar from which we should start drawing
      if(!DetermineFirstTrueBar())
         return(RESET);
      //--- If you reached this point, it means that OnCalculate() will return non-zero value and this needs to be saved
      OC_prev_calculated=rates_total;
     }

Şimdi, belirli bir kontrol her başarısız olduğunda, program bir sonraki tik veya zamanlayıcı olayında başka bir girişimde bulunmak için geri adım geri gidecektir. Zamanlayıcıda, OnCalculate() fonksiyonunun dışında daha derin bir geçmiş yüklemek için de kontrol yapmamız gerekiyor:

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//--- If a deeper history has been loaded
   if(!CheckLoadedHistory())
      OC_prev_calculated=0;
//--- If for some reason calculations have not been completed or
//    a deeper history has been loaded or
//    gaps in the history have been filled, 
//    then make another attempt without waiting for the new tick
   if(OC_prev_calculated==0)
     {
      OnCalculate(OC_rates_total,OC_prev_calculated,
                  OC_time,OC_open,OC_high,OC_low,OC_close,
                  OC_tick_volume,OC_volume,OC_spread);
     }
  }

Şimdi, OnCalculate() fonksiyonuna yerleştirilecek iki ana döngüyü yazmamız gerekiyor:

  • İlk döngü, gösterge serilerindeki boşlukları önlemek için verileri, "değeri tüm araçlarla elde etme" ilkesine dayalı olarak hazırlayacaktır. Bunun arkasındaki fikir basittir: değerin elde edilememesi durumunda belirli sayıda girişimde bulunulacaktır. Bu döngüde, sembollerin zaman değerleri ve volatilite gösterge (ATR) değerleri ayrı dizilere kaydedilecektir.
  • İkinci ana döngüde, gösterge tamponları doldurulurken, diğer sembollerin zaman dizileri, mevcut sembol zamanı ile karşılaştırma ve tüm çizim serilerinin senkronizasyonu için gerekli olacaktır.

İlk döngünün kodu aşağıda verilmektedir:

//--- Prepare data for drawing
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If the symbol is available
      if(symbol_names[s]!=empty_symbol)
        {
         double percent=0.0; // For the purpose of calculating the progress percentage
         msg_last=msg_sync_update="Preparing data ("+IntegerToString(rates_total)+" bars) : "+
                      symbol_names[s]+"("+(string)(s+1)+"/"+(string)(SYMBOLS_COUNT)+") - 00% ... ";
         //--- Print the message
         ShowCanvasMessage(msg_sync_update);
         //--- Control every value of the array
         for(int i=limit; i<rates_total; i++)
           {
            PrepareData(i,s,time);
            //--- Refresh the message once every 1000 bars
            if(i%1000==0)
              {
               //--- Progress percentage
               ProgressPercentage(i,s,percent);
               //--- Print the message
               ShowCanvasMessage(msg_sync_update);
              }
            //--- Check the subwindow size once every 2000 bars
            //    and if the size has changed, adjust the canvas size to it
            if(i%2000==0)
               OnSubwindowChange();
           }
        }
     }

Değerleri kopyalama ve kaydetme ana fonksiyonu PrepareData() yukarıdaki kodda vurgulanmıştır. Bir de henüz değerlendirilmeyen yeni bir fonksiyon vardır: ProgressPercentage(). Bu, kullanıcıya işlemin ne kadar süreceğini bildirmek için mevcut işlemin ilerleme yüzdesini hesaplar.

PrepareData() fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Preparing data before drawing                                    |
//+------------------------------------------------------------------+
void PrepareData(int bar_index,int symbol_number,datetime const &time[])
  {
   int attempts=100; // Number of copying attempts
//--- Time of the bar of the specified symbol and time frame
   datetime symbol_time[];
//--- Array for copying indicator values
   double atr_values[];
//--- If within the area of the current time frame bars
   if(time[bar_index]>=limit_time[symbol_number])
     {
      //--- Copy the time
      for(int i=0; i<attempts; i++)
        {
         if(CopyTime(symbol_names[symbol_number],0,time[bar_index],1,symbol_time)==1)
           {
            tmp_symbol_time[symbol_number].time[bar_index]=symbol_time[0];
            break;
           }
        }
      //--- Copy the indicator value
      for(int i=0; i<attempts; i++)
        {
         if(CopyBuffer(symbol_handles[symbol_number],0,time[bar_index],1,atr_values)==1)
           {
            tmp_atr_values[symbol_number].value[bar_index]=atr_values[0];
            break;
           }
        }
     }
//--- If outside the area of the current time frame bars, set an empty value
   else
      tmp_atr_values[symbol_number].value[bar_index]=EMPTY_VALUE;
  }

ProgressPercentage() fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Calculating progress percentage                                  |
//+------------------------------------------------------------------+
void ProgressPercentage(int bar_index,int symbol_number,double &percent)
  {
   string message_text="";
   percent=(double(bar_index)/OC_rates_total)*100;
//---
   if(percent<=9.99)
      message_text="0"+DoubleToString(percent,0);
   else if(percent<99)
      message_text=DoubleToString(percent,0);
   else
      message_text="100";
//---
   msg_last=msg_sync_update="Preparing data ("+(string)OC_rates_total+" bars) : "+
            symbol_names[symbol_number]+
            "("+(string)(symbol_number+1)+"/"+(string)SYMBOLS_COUNT+") - "+message_text+"% ... ";
  }

Gösterge tamponları, OnCalculate() fonksiyonunun ikinci ana döngüsünde doldurulur:

//--- Fill indicator buffers
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- If the specified symbol does not exist, zero out the buffer
      if(symbol_names[s]==empty_symbol)
         ArrayInitialize(atr_buffers[s].data,EMPTY_VALUE);
      else
        {
         //--- Generate a message
         msg_last=msg_sync_update="Updating indicator data: "+
                      symbol_names[s]+"("+(string)(s+1)+"/"+(string)SYMBOLS_COUNT+") ... ";
         //--- Print the message
         ShowCanvasMessage(msg_sync_update);
         //--- Fill indicator buffers with values
         for(int i=limit; i<rates_total; i++)
           {
            FillIndicatorBuffers(i,s,time);
            //--- Check the subwindow size once every 2000 bars
            //    and if the size has changed, adjust the canvas size to it
            if(i%2000==0)
               OnSubwindowChange();
           }
        }
     }

Yukarıdaki kodda vurgulanan dize FillIndicatorBuffers() fonksiyonunu içerir. Burası, göstergenin çizim serisini grafikte görüntülemeden önce son işlemlerin gerçekleştirildiği yerdir:

//+------------------------------------------------------------------+
//| Filling indicator buffers                                        |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(int bar_index,int symbol_number,datetime const &time[])
  {
//--- For the purpose of checking the obtained indicator value
   bool check_value=false;
//--- Counter of the current time frame bars
   static int bars_count=0;
//--- Zero out the counter of the current time frame bars at the beginning of the symbol's time series
   if(bar_index==0)
      bars_count=0;
//--- If within the area of current time frame bars and the counter is smaller 
//    than the specified indicator period, increase the counter
   if(bars_count<IndicatorPeriod && time[bar_index]>=limit_time[symbol_number])
      bars_count++;
//--- If within the indicator area and the time of the current symbol coincides with the time of the specified symbol
   if(bars_count>=IndicatorPeriod && 
      time[bar_index]==tmp_symbol_time[symbol_number].time[bar_index])
     {
      //--- If the value obtained is not empty
      if(tmp_atr_values[symbol_number].value[bar_index]!=EMPTY_VALUE)
        {
         check_value=true;
         atr_buffers[symbol_number].data[bar_index]=tmp_atr_values[symbol_number].value[bar_index];
        }
     }
//--- Set an empty value in case of failure to set a higher value
   if(!check_value)
      atr_buffers[symbol_number].data[bar_index]=EMPTY_VALUE;
  }

OnCalculate() fonksiyonunun sonunda, tuvali silmemiz, mesajların değişkenlerini sıfırlamamız ve çizelgeyi yenilememiz gerekir. Son olarak rates_total dizi boyutu döndürülecektir ve bunun ardından OnCalculate() fonksiyonunda sonraki her bir tik veya zamanlayıcı olayınca son değer yeniden hesaplanacaktır.

Bunlar, ikinci ana döngü ile fonksiyon tarafından döndürülen değer arasına yerleştirilecek kod dizeleridir:

//--- Delete the canvas
   DeleteCanvas();
//--- Set indicator levels
   SetIndicatorLevels();
//--- Zero out variables
   msg_last="";
   msg_sync_update="";
//--- Refresh the chart
   ChartRedraw();

SetIndicatorLevels() yatay seviyeleri ayarlama fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------------+
//| Setting indicator levels                                               |
//+------------------------------------------------------------------------+
void SetIndicatorLevels()
  {
//--- Get the indicator subwindow number
   subwindow_number=ChartWindowFind(0,subwindow_shortname);
//--- Set levels
   for(int i=0; i<LEVELS_COUNT; i++)
      CreateHorizontalLine(0,subwindow_number,
                            prefix+"level_0"+(string)(i+1)+"",
                            CorrectValueBySymbolDigits(indicator_levels[i]*_Point),
                            1,STYLE_DOT,clrLightSteelBlue,false,false,false,"\n");
  }
//+------------------------------------------------------------------------+
//| Adjusting the value based on the number of digits in the price (double)|
//+------------------------------------------------------------------------+
double CorrectValueBySymbolDigits(double value)
  {
   return(_Digits==3 || _Digits==5) ? value*=10 : value;
  }

CreateHorizontalLine() belirtilen özelliklere sahip bir yatay seviye ayarlama fonksiyon kodu aşağıdaki gibidir:

//+------------------------------------------------------------------+
//| Creating a horizontal line at the price level specified          |
//+------------------------------------------------------------------+
void CreateHorizontalLine(long            chart_id,      // chart id
                          int             window_number, // window number
                          string          object_name,   // object name
                          double          price,         // price level
                          int             line_width,    // line width
                          ENUM_LINE_STYLE line_style,    // line style
                          color           line_color,    // line color
                          bool            selectable,    // cannot select the object if FALSE
                          bool            selected,      // line is selected
                          bool            back,          // background position
                          string          tooltip)       // no tooltip if "\n"
  {
//--- If the object has been created successfully
   if(ObjectCreate(chart_id,object_name,OBJ_HLINE,window_number,0,price))
     {
      //--- set its properties
      ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTABLE,selectable);
      ObjectSetInteger(chart_id,object_name,OBJPROP_SELECTED,selected);
      ObjectSetInteger(chart_id,object_name,OBJPROP_BACK,back);
      ObjectSetInteger(chart_id,object_name,OBJPROP_STYLE,line_style);
      ObjectSetInteger(chart_id,object_name,OBJPROP_WIDTH,line_width);
      ObjectSetInteger(chart_id,object_name,OBJPROP_COLOR,line_color);
      ObjectSetString(chart_id,object_name,OBJPROP_TOOLTIP,tooltip);
     }
  }

Grafik nesnelerini silme fonksiyonları:

//+------------------------------------------------------------------+
//| Deleting levels                                                  |
//+------------------------------------------------------------------+
void DeleteLevels()
  {
   for(int i=0; i<LEVELS_COUNT; i++)
      DeleteObjectByName(prefix+"level_0"+(string)(i+1)+"");
  }
//+------------------------------------------------------------------+
//| Deleting vertical lines of the beginning of the series           |
//+------------------------------------------------------------------+
void DeleteVerticalLines()
  {
   for(int s=0; s<SYMBOLS_COUNT; s++)
      DeleteObjectByName(prefix+symbol_names[s]+": begin time series");
  }
//+------------------------------------------------------------------+
//| Deleting the object by name                                      |
//+------------------------------------------------------------------+
void DeleteObjectByName(string object_name)
  {
//--- If such object exists
   if(ObjectFind(0,object_name)>=0)
     {
      //--- If an error occurred when deleting, print the relevant message
      if(!ObjectDelete(0,object_name))
         Print("Error ("+IntegerToString(GetLastError())+") when deleting the object!");
     }
  }

Aşağıdaki kod OnDeinit() fonksiyonuna eklenmelidir:

Artık her şey hazırdır ve tam bir test yapılabilir. Penceredeki çubukların maksimum sayısı, terminal ayarlarının Grafikler sekmesinden ayarlanabilir. Göstergenin ne kadar hızlı çalışmaya hazır olacağı penceredeki çubuk sayısına bağlıdır.

Şekil 1. Terminal ayarlarında maksimum çubuk sayısını ayarlama

Şekil 1. Terminal ayarlarında maksimum çubuk sayısını ayarlama

Maksimum çubuk sayısını ayarladıktan sonra, göstergenin değişiklikleri uygulaması için terminal yeniden başlatılmalıdır, aksi halde önceki değer kullanılacaktır.

Göstergeyi grafiğe yüklerken, tüm sembollerin veri hazırlığı ilerlemesini görebilirsiniz:

Şekil 2. Veri hazırlığı sırasında tuval üzerindeki mesaj

Şekil 2. Veri hazırlığı sırasında tuval üzerindeki mesaj

Aşağıda, göstergeyi 20 dakikalık bir zaman aralığında görüntüleyen ekran görüntüsünü görebilirsiniz:

Şekil 3. 20 dakikalık zaman aralığında çok sembollü ATR göstergesi

Şekil 3. 20 dakikalık zaman aralığında çok sembollü ATR göstergesi

"True" çubuklarının başlangıcı grafikte dikey çizgiler ile işaretlenir. Aşağıdaki ekran görüntüsü, NZDUSD (sarı çizgi) için true çubukların 2000'den (MetaQuotes-Demo sunucusu) itibaren başladığını, ancak diğer tüm döviz çiftleri true çubuklarının 1999'un başlarında göründüğünü, bu nedenle yalnızca bir çizginin görüntülendiğini göstermektedir (bunların tamamı aynı tarihtedir). Dönem ayırıcılarının 1999 öncesinde daha küçük bir aralığa sahip olduğunu da fark edebiliriz ve çubukların zamanını analiz ettiğinizde, bunların günlük çubuklar olduğunu görebilirsiniz.

Şekil 4. Dikey çizgiler her sembol için true çubukların başlangıcını işaretler

 

Sonuç

Makale burada bitirilebilir. Açıklanan kaynak kodu makaleye eklenmiştir ve buradan indirilebilir. Sonraki makalelerden birinde, volatiliteyi analiz edecek bir alım satım sistemi uygulamaya çalışacağız ve ortaya ne çıkacağına bakacağız.

MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/752

Ekli dosyalar |
multisymbolatr.mq5 (47.42 KB)
Çok Para Birimli, Çok Sistemli bir Uzman Danışman Oluşturma Çok Para Birimli, Çok Sistemli bir Uzman Danışman Oluşturma
Makale, eş zamanlı olarak birçok sembolün alım satımını yapan veya birkaç alım satım sistemini kullanan bir Uzman Danışman yapısını tanıtmaktadır. Tüm EA'larınız için optimum giriş parametrelerini zaten tanımladıysanız ve ayrı olarak bunlardan her biri için iyi geri dönük test sonuçları elde ettiyseniz, kendinize sorun: Tüm stratejilerinizin bir araya getirildiği tüm EA'larınız eş zamanlı olarak test edilseydi sonuçları ne olurdu?
MQL5 Tarif Defteri Gösterge Alt Pencere Kontrolleri - Kaydırma Çubuğu MQL5 Tarif Defteri Gösterge Alt Pencere Kontrolleri - Kaydırma Çubuğu
Çeşitli kontrolleri keşfetmeye devam edelim ve bu sefer dikkatimizi kaydırma çubuğuna çevirelim. Tıpkı "MQL5 Tarif Defteri: Gösterge Alt Pencere Kontrolleri - Düğmeler" başlıklı önceki makaledeki gibi, tüm işlemler gösterge alt penceresinde gerçekleştirilecektir. OnChartEvent() fonksiyonundaki olaylar ile çalışmanın ayrıntılı bir açıklamasını sağladığından, yukarıda bahsedilen makaleyi okumak için biraz zaman ayırın, ancak bu noktaya bu makalede yalnızca üstünkörü değinilecektir. Açıklayıcı olması açısından, bu sefer MQL5 kaynakları kullanılarak elde edilebilen tüm finansal enstrüman özelliklerinin geniş bir listesi için dikey bir kaydırma çubuğu oluşturacağız.
Kagi Grafik Göstergesi Kagi Grafik Göstergesi
Makale, çeşitli grafik seçenekleri ve ek fonksiyonlar içeren Kagi grafik göstergesini önermektedir. Ayrıca, gösterge grafik oluşturma ilkesi ve MQL5 uygulama özellikleri de göz önünde bulundurulmuştur. Alım satımda uygulanmasının en popüler örnekleri - Yin/Yang değişim stratejisi, trend çizgisinden uzaklaşan ve sürekli olarak artan "omuzlar"/azalan "beller" görüntülenir.
MQL5 Tarif Defteri Gösterge Alt Pencere Kontrolleri - Düğmeler MQL5 Tarif Defteri Gösterge Alt Pencere Kontrolleri - Düğmeler
Bu makalede, düğme kontrolleri ile bir kullanıcı arayüzünü geliştirme örneğini ele alacağız. Kullanıcı ile etkileşim fikrini iletmek için, imleç üzerlerine geldiğinde düğmelerin renkleri değişecektir. İmleç bir düğmenin üzerindeyken, düğme rengi hafifçe koyulaşacak ve düğmeye tıklandığında büyük ölçüde koyulaşacaktır. Ayrıca, her düğmeye araç ipuçları ekleyerek sezgisel bir arayüz oluşturacağız.