Русский 中文 Español Deutsch 日本語 Português
Statistical distributions in the form of histograms without indicator buffers and arrays

Statistical distributions in the form of histograms without indicator buffers and arrays

MetaTrader 5Examples | 16 January 2017, 08:36
19 507 6
Sergey Pavlov
Sergey Pavlov

Introduction

Histograms allow researchers to visually evaluate the distribution of statistical data grouped by the frequency of their penetration into a certain (predetermined) interval.

Histograms and their use in the statistical data analysis are a well-studied topic with multiple articles devoted to it [1, 2, 3, 4, 5, 6, 7] and a large number of CodeBase examples [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. However, applied algorithms are based on using indicator buffers or arrays. In this article, we consider the possibility of building statistical distributions of different market characteristics without complicated calculations, sorting, sampling, etc. In order to achieve this, we will use the "graphical" memory — the section storing the properties of graphical objects. Since we need graphical objects when plotting custom histograms, we can use their "hidden" abilities and rich functionality to full capacity.

The article's objective is to offer simple solutions to standard statistical problems. The article focuses mainly on visualization of statistical distributions and their basic characteristics. We are not going to dwell on histograms interpretation and their practical advantages.

Histogram plotting basics

A histogram is a bar chart of frequencies. One of the axes represents variable values, while another — the frequency of these values' occurrence. The height of each bar shows the frequency (number) of values ​​belonging to the respective interval equal to the column width. Such diagrams are usually displayed horizontally, i.e. variable values are located on a horizontal axis, while frequency — on a vertical one. Using histograms to represent studied data makes statistical data more visible, as well as easier to comprehend and analyze.

In this article, we will focus on the vertical histograms of the variation series: price values of the analyzed parameters are to be located on the vertical axis in ascending order, while frequency is to be located on the horizontal axis (Fig. 1). Incoming price data are distributed and grouped on the current bar and can be displayed relative to its axis from the left, right or both sides simultaneously.

Fig. 1. Vertical histogram of Bid and Ask prices distribution 

Fig. 1. Vertical histogram of Bid and Ask prices distribution

Let's consider a specific task:

  • plot the Bid and Ask prices distribution histogram; 
  • locate Ask price data to the right of the current bar, while Bid — to the left;
  • calculate the frequency of each incoming price value when a new tick arrives, i.e. the histogram interval is equal to the current symbol's point size.

Now, let's make the condition more complicated: no indicator buffers, arrays or structures are allowed.

How to solve this problem? 

First, we need to figure out where to store the accumulated frequency for each histogram column. Even on Fig. 1, we can see that there may be an indefinite number of histogram bars. The first thing that comes to mind is to use a dynamic array since the possible price range (number of bars) on the selected time interval of a price chart is not known in advance. But arrays are not allowed by the problem conditions.

Second, we should solve the search and sorting task: where and how to search for data for the histogram recalculation and redrawing.

It turns out the MQL5 language developers have already created the necessary (and quite powerful) functionality. It is based on using "hidden" (unobvious) features of the graphical object function groups. Each object has its properties — the variables created together with the object and used for storing various multiple parameters. Some properties can be used not quite for their designated purpose, while keeping the functionality intact. Let's call these properties the "graphical" memory. In other words, if you need to save a variable and receive its value, create a graphical object and assign the variable value to a certain property.

Thus, graphical object properties act as a more efficient kind of the terminal global variables. The global variables exist in the client terminal for four weeks since the last access to them and are automatically deleted afterwards. The graphical memory exists till a graphical object is removed, thus providing us with ample opportunities. 

What graphical object properties can we use in the graphical memory

When creating a graphical object, we should assign a unique name to it. The name is a text string which may consist of substrings, while a substring in turn may contain formatted basic data types: integer, boolean, floating-point, color, date and time. Thus, OBJPROP_NAME can store variables mainly for reading data.

Yet another property that can be used is the OBJPROP_TEXT object description. This is also a text string with much greater possibilities as compared to the previous property. Both reading and writing variables are allowed in the property field.

Any graphical object has its own coordinates: price OBJPROP_PRICE and time OBJPROP_TIME. They can also be used in the graphical memory.

Let's get back to our objective. The frequencies are to be stored in the OBJPROP_TEXT property, while Bid and Ask prices — in the OBJPROP_NAME. The code of the function creating objects and collecting the frequencies is provided below:

void DrawHistogram(bool draw,     // draw a histogram to the left or to the right
                   string h_name, // unique prefix for the object name
                   double price,  // price (analyzed parameter)
                   datetime time, // bind a histogram to the current bar
                   int span,      // analyzed parameter digit capacity
                   int swin=0)    // histogram window
  {
   double y=NormalizeDouble(price,span);
   string pfx=DoubleToString(y,span);
// if draw=true, draw the histogram to the right
   if(draw)
     {
      string name="+ "+h_name+pfx;                   // object name: prefix+price
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);    // create the object
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // set the object color
      ObjSet;                                                // code shortening macro
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {// if the resulting price entered the sample for the first time
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");          // the price freuqency is 1
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // define the time coordinate
        }
      else
        {// if the resulting price entered the sample not for the first time
         string str=ObjectGetString(0,name,OBJPROP_TEXT);    // get the property value
         string strint=StringSubstr(str,1);                  // highlight the substring
         long n=StringToInteger(strint);                     // get a frequency for further calculations
         n++;                                                // increase the value by 1
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // write a new value to the property
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//define the time coordinate
        }
     }
// if draw=false, write the histogram to the left
   if(!draw)
     {
      string name="- "+h_name+pfx;
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active);
      ObjSet;
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize);
        }
      else
        {
         string str=ObjectGetString(0,name,OBJPROP_TEXT);
         string strint=StringSubstr(str,1);
         long n=StringToInteger(strint);
         n++;
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n);
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n);
        }
     }
   ChartRedraw();
  }

The function consists of two similar parts for Bid and Ask each. Therefore, comments are present in the first block only.

You may ask, what is the point in doubling the price in the object name if it is available in the OBJPROP_PRICE property anyway?

Let's dwell on that a bit. When a new price arrives, we should define the appropriate column for it and increase the frequency value accordingly. If we used the price coordinate from its native property, we would have to pass through all graphical objects, request the property value and compare it with the received price. Only after that, we would be able to write the new value to an appropriate column. However, comparing real double-type numbers is quite a backbreaking task greatly reducing the efficiency of the algorithm. The more elegant solution is to write the new frequency directly to the necessary destination right after a new price value arrives. How can we achieve that? When we create a new graphical object with a name similar to an existing object's one, the new object is not created and the property fields are not set to zero. In other words, we create objects neglecting the fact that they will be doubled. There is no need in extra checks since no copies and clones are to be created. The terminal and graphical objects functionality will take care of that. We only have to define if the price entered the sample for the first time or not. To do this, use the asterisk (*) prefix in the OBJPROP_TEXT object property of the DrawHistogram() function. No asterisk means the price entered the sample for the first time. Indeed, when we create a new object, the field is empty. A frequency value with a specified prefix is to be stored there during subsequent calls.

Next, let's shift the histogram to the right. When a new bar appears, the chart shifts to the left, however we need the histogram to be displayed on the current bar. In other words, it should shift in the opposite direction. Here is how we can achieve that:

//--- Shift the diagrams to the new bar
   if(time[0]>prevTimeBar) // define a new bar arrival
     {
      prevTimeBar=time[0];
      // pass through all graphical objects
      for(int obj=ObjectsTotal(0,-1,-1)-1;obj>=0;obj--)
        {
         string obj_name=ObjectName(0,obj,-1,-1);               // get a name of a found object
         if(obj_name[0]==R)                                     // search for the histogram element prefix
           {                                                    // if the histogram element is found
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // set a new coordinate value
                             0,time[0]);                        // for "0" anchor point
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);// read the variable from the object property
            string strint=StringSubstr(str,1);                  // separate a substring from the received variable
            long n=StringToInteger(strint);                     // convert the string into a long variable
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // calculate the new coordinate value
                             1,time[0]+hsize*n);                // for "1" anchor point
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,
                             color_R_passive);                  // change the color of the shifted histogram element
           }
         if(obj_name[0]==L)
           {
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,0,time[0]);
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);
            string strint=StringSubstr(str,1);
            long n=StringToInteger(strint);
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n);
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,color_L_passive);
           }
        }
      ChartRedraw();
     }

That is all. The Bid and Ask prices histogram is plotted on the chart, while the program does not use a single array or an indicator buffer. The complete code of the solution is attached below.


The video displays a ready-made solution of a similar problem with the use of the graphical memory. The code itself can be found in the CodeBase: "Histogram bid and ask prices" indicator (in Russian).

Plotting histograms in the main window

Now that we have considered the functionality programming using the graphical memory, let's plot several histograms for standard indicators. The plotting principle is similar to the considered one. However, indicator values on the current bar are to be used instead of Bid and Ask prices.

     1. iMA indicator. Let's use two indicators having different averaging periods and build histograms. Here is the code:

input int               period_MA1=20;       // Averaging period of iMA1 
input int               period_MA2=14;       // Averaging period of iMA2 
//---- indicator buffers
double      MA1[];
double      MA2[];
//---- handles for indicators
int         iMA1_handle;
int         iMA2_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iMA1_handle=iMA(_Symbol,_Period,period_MA1,0,MODE_SMA,PRICE_CLOSE);
   iMA2_handle=iMA(_Symbol,_Period,period_MA2,0,MODE_SMA,PRICE_CLOSE);
   ArraySetAsSeries(MA1,true);
   ArraySetAsSeries(MA2,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iMA1_handle,0,0,1,MA1);
   CopyBuffer(iMA2_handle,0,0,1,MA2);

   DrawHistogram(true,"iMA("+(string)period_MA1+")=",MA1[0],time[0],_Digits);
   DrawHistogram(false,"iMA("+(string)period_MA2+")=",MA2[0],time[0],_Digits);

//--- Shift the diagrams to a new bar
   if(time[0]>prevTimeBar) // define a new bar arrival
     {
      prevTimeBar=time[0];
      // pass through all graphical objects
      for(int obj=ObjectsTotal(0,-1,-1)-1;obj>=0;obj--)
        {
         string obj_name=ObjectName(0,obj,-1,-1);               // get a name of a found object
         if(obj_name[0]==R)                                     // search for the histogram element prefix
           {                                                    // if the histogram element is found
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // set a new coordinate value
                             0,time[0]);                        // for "0" anchor point
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);// read the variable from the object property
            string strint=StringSubstr(str,1);                  // separate a substring from the received variable
            long n=StringToInteger(strint);                     // convert the string into a long variable
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,           // calculate the new coordinate value
                             1,time[0]+hsize*n);                // for "1" anchor point
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,
                             color_R_passive);                  // change the color of the shifted histogram element
           }
         if(obj_name[0]==L)
           {
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,0,time[0]);
            string str=ObjectGetString(0,obj_name,OBJPROP_TEXT);
            string strint=StringSubstr(str,1);
            long n=StringToInteger(strint);
            ObjectSetInteger(0,obj_name,OBJPROP_TIME,1,time[0]-hsize*n);
            ObjectSetInteger(0,obj_name,OBJPROP_COLOR,color_L_passive);
           }
        }
      ChartRedraw();
     }
   return(rates_total);
  }

Fig. 2. Histogram of the two iMA indicators

Fig. 2. Histogram of the two iMA indicators 

     2. iBands indicator. Let's plot the histograms for the indicator's upper (UPPER_BAND) and lower (LOWER_BAND) borders. The abridged code is shown below.

input int   period_Bands=14;           // Averaging period of iBands 
//---- indicator buffers
double      UPPER[];
double      LOWER[];
//---- handles for indicators
int         iBands_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iBands_handle=iBands(_Symbol,_Period,period_Bands,0,5.00,PRICE_CLOSE);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iBands_handle,1,0,1,UPPER);
   CopyBuffer(iBands_handle,2,0,1,LOWER);

   DrawHistogram(true,"iBands(UPPER)=",UPPER[0],time[0],_Digits);
   DrawHistogram(false,"iBands(LOWER)=",LOWER[0],time[0],_Digits);

//--- Shift the diagram to a new bar
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

Fig. 3. Histogram of the iBands indicator's Bollinger bands

Fig. 3. Histogram of the iBands indicator's Bollinger bands 

We can get quite interesting histograms for the Bollinger indicator if we use all three indicator buffers (0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND). Let's place the histogram of the upper and lower bands to the right of the current bar, while the main line histogram — to the left of the zero bar. The result is shown in the image below:

Fig. 4. Bollinger bands histogram using three indicator buffers

Fig. 4. Bollinger bands histogram using three indicator buffers  

The code for this histogram version is provided below. Note that the two histograms (for the indicator upper and lower borders) are actually displayed to the right. They are perceived as a single diagram visually.

input int   period_Bands=20;       // Averaging period of iBands 
//---- indicator buffers
double      BASE[];
double      UPPER[];
double      LOWER[];
//---- handles for indicators
int         iBands_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iBands_handle=iBands(_Symbol,_Period,period_Bands,0,10.00,PRICE_CLOSE);
   ArraySetAsSeries(BASE,true);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iBands_handle,0,0,1,BASE);
   CopyBuffer(iBands_handle,1,0,1,UPPER);
   CopyBuffer(iBands_handle,2,0,1,LOWER);

   DrawHistogram(true,"iBands(UPPER)=",UPPER[0],time[0],_Digits);
   DrawHistogram(true,"iBands(LOWER)=",LOWER[0],time[0],_Digits);
   DrawHistogram(false,"iBands(LOWER)=",BASE[0],time[0],_Digits);

//--- Shift the diagram to a new bar
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

So far, we examined the variation series, in which the range of the analyzed values (the variant) was equal to the current symbol's point (_Point) size. In other words, all price values were drawn as a separate histogram column.

It should be highlighted how gracefully and seamlessly the variation series were sorted (ranked) using the terminal's built-in functionality. Not a single line of code was written for that.

Now, let's modify the distribution range having multiple variant values using a single variable and the power of the graphical objects. The DrawHistogram() function mentioned above features the span input — analyzed parameter's decimal capacity. By varying the decimal capacity, we are able to change the interval for calculating the histogram frequencies. Since the price is included into the graphical object name, selection is performed automatically when creating and adjusting the objects' properties. See how sorting and grouping tasks are easily solved using the graphical memory with no programming.

Fig. 5. Bid and Ask prices histogram with the increased column interval

Fig. 5. Bid and Ask prices histogram with the increased column interval 

Thus, we can easily plot histograms in the main chart window. Now, let's see how to plot similar diagrams in the additional chart windows.

Histograms in additional windows

The principle of creating statistical distributions in additional windows is similar to that for the main window. The difference is that the values ​​of the analyzed variables can be negative. Besides, they may have arbitrary digit capacity. In the main window, the price scale has a predefined digit capacity that can be obtained using the _Digits variable. In additional windows, the variables may be both positive and negative. Besides, the number of decimal places is not limited. This should be considered when setting the input parameters of the DrawHistogram() function for building histograms. Also, make sure to set the additional window index. The main window equal to zero is used in the function by default.

Examples of histograms in additional windows

The best way to digest information is to use examples. Let's consider a few examples of plotting histograms in additional windows for standard technical indicators.

     1. iChaikin indicator.

Fig. 6. Chaikin Oscillator histogram

Fig. 6. Chaikin Oscillator histogram

The abridged code of the indicator is as follows:

input int   period_fast=3;       // Averaging period fast of iChaikin 
input int   period_slow=10;      // Averaging period of slow iChaikin 
//---- indicator buffers
double      CHO[];
//---- handles for indicators
int         iChaikin_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iChaikin_handle=iChaikin(_Symbol,_Period,period_fast,period_slow,MODE_SMA,VOLUME_TICK);
   ArraySetAsSeries(CHO,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iChaikin_handle,0,0,2,CHO);

   DrawHistogram(true,"iChaikin=",CHO[0],time[0],0,1);
   
//--- Shift the diagram to a new bar
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

     2. iCCI indicator.

Fig. 7. Commodity Channel Index histogram

Fig. 7. Commodity Channel Index histogram

The abridged code:

input int   period_CCI=14;             // Averaging period of iCCI 
//---- indicator buffers
double      CCI[];
//---- handles for indicators
int         iCCI_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iCCI_handle=iCCI(_Symbol,_Period,period_CCI,PRICE_CLOSE);
   ArraySetAsSeries(CCI,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iCCI_handle,0,0,2,CCI);

   DrawHistogram(true,"iCCI=",CCI[0],time[0],0,1);
   
//--- Shift the diagram to a new bar
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

     3. Indicators: iEnvelopesiATR, iMACD. The combination of histograms for three indicators. Each of the indicators is displayed in a separate window.

Fig. 8. Histograms of three indicators: iEnvelopes, iATR and iMACD

Fig. 8. Histograms of three indicators: iEnvelopes, iATR and iMACD 

The abridged code for the set of histograms on Fig. 8.

input int   period_Envelopes=14;       // Averaging period of iEnvelopes 
input int   period_ATR=14;             // Averaging period of iATR 
input int   period_fast=12;            // Averaging period fast of iMACD 
input int   period_slow=26;            // Averaging period of slow iMACD 
//---- indicator buffers
double      UPPER[];
double      LOWER[];
double      ATR[];
double      MAIN[];
double      SIGNAL[];
//---- handles for indicators
int         iEnvelopes_handle;
int         iATR_handle;
int         iMACD_handle;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   ObjectsDeleteAll(0,-1,-1);
   ChartRedraw();
   iEnvelopes_handle=iEnvelopes(_Symbol,_Period,period_Envelopes,0,MODE_SMA,PRICE_CLOSE,0.1);
   iATR_handle=iATR(_Symbol,_Period,period_ATR);
   iMACD_handle=iMACD(_Symbol,_Period,period_fast,period_slow,9,PRICE_CLOSE);
   ArraySetAsSeries(UPPER,true);
   ArraySetAsSeries(LOWER,true);
   ArraySetAsSeries(ATR,true);
   ArraySetAsSeries(MAIN,true);
   ArraySetAsSeries(SIGNAL,true);
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   ArraySetAsSeries(time,true);
   
   CopyBuffer(iEnvelopes_handle,0,0,1,UPPER);
   CopyBuffer(iEnvelopes_handle,1,0,1,LOWER);
   CopyBuffer(iATR_handle,0,0,1,ATR);
   CopyBuffer(iMACD_handle,0,0,1,MAIN);
   CopyBuffer(iMACD_handle,1,0,1,SIGNAL);

   DrawHistogram(true,"iEnvelopes.UPPER=",UPPER[0],time[0],_Digits);
   DrawHistogram(false,"iEnvelopes.LOWER=",LOWER[0],time[0],_Digits);
   DrawHistogram(true,"iATR=",ATR[0],time[0],_Digits+1,1);
   DrawHistogram(true,"iMACD.SIGNAL=",SIGNAL[0],time[0],_Digits+1,2);
   DrawHistogram(false,"iMACD.MAIN=",MAIN[0],time[0],_Digits+1,2);

//--- Shift the diagram to a new bar
   if(time[0]>prevTimeBar)
     {
     ...
     }
   return(rates_total);
  }

After considering the plotting of various histograms of the market parameters' statistical distributions, we can conclude that:

  • МТ5 trading terminal functionality;
  • MQL5 graphical objects' features;
  • graphical memory (little-known features of graphical objects);
  • the simple DrawHistogram() function

allow us to calculate and visualize the cumulative histograms of multiple indicators — from standard to custom ones. However, this is insufficient for a more detailed analysis of market parameters.

Numerical parameters of statistical distributions

The first part of the article dealt with the histogram plotting method suitable for novice programmers. Provided examples are easily projected onto a large class of problems related to statistical distributions and their visualization. However, in order to conduct more in-depth histogram analysis, we need the most versatile and advanced programming methods, like OOP. Let's define the statistical distribution parameters that are to be of main interest for the readers.

First, we are interested in numerical parameters of variation series:

  • arithmetic mean (average frequency);
  • weighted arithmetic mean;
  • variance;
  • standard deviation.

Second, we need to visualize them. Of course, all this can be implemented using procedural style, but we are going to apply object-oriented programming in this article to solve the tasks.

CHistogram class

The class functionality allows displaying histograms and main statistical distribution parameters on a chart. Let's consider the basic methods.

Method: CHistogram class constructor.

Initializes the class instance. 

void CHistogram(
   string name,                     // unique name prefix
   int    hsize,                    // diagram scale
   int    width,                    // histogram column line width
   color  active,                   // active line color
   color  passive,                  // passive line color
   bool   Left_Right=true,          // left=false or right=true
   bool   relative_frequency=false, // relative or absolute histogram
   int    sub_win=0                 // histogram window index
   );

Parameters:

 name

    [in] Unique name prefix for all histogram columns. 

 hsize

    [in] Histogram scale.

 width

    [in] Histogram column line width.

 active

    [in] Color of histogram columns updated on the current bar.

 passive

    [in] Color of histogram columns that are not updated on the current bar.

 Left_Right=true

    [in] Histogram direction. false — histogram located to the left of the current bar, true — to the right.

 relative_frequency=false

    [in] Frequencies accounting method. false — absolute frequency values, true — relative frequency values.

 sub_win=0

    [in] Histogram window index. 0 — main chart window. 

Return value:

 No return value. If successful, the class instance with the specified parameters is created.

 

Method: DrawHistogram display.

Displays histogram columns: creates new columns, edits existing ones, saves frequency values in the graphical memory. Displays the histogram on the current bar.

void DrawHistogram(
   double price,  // variant value
   datetime time  // current bar time
   );

Parameters:

 price

    [in] Analyzed market parameter variant value.

 time

    [in] Time of the current bar. The histogram axis is on the bar.  

Return value:

 No return value. If successful, a new histogram bar is created or an existing one is created. If a new bar appears, the histogram is shifted so that the axis is located on the current bar. 

 

Method: calculating the HistogramCharacteristics histogram parameters. 

Returns calculated variation series parameters in sVseries-type variable.

sVseries HistogramCharacteristics();

Parameters:

 No input parameters.

Return value:

 If successful, an sVseries-type variable value is returned.

 

Structure for receiving the current values of the histogram parameters (sVseries).

Structure for storing the last values of the statistical distribution parameters. The structure is designed for receiving the most needed variation series data. 

struct sVseries
  {
   long     N;    // total number of observations
   double   Na;   // frequencies average value
   double   Vmax; // maximum variant value
   double   Vmin; // minimum variant value
   double   A;    // series amplitude
   double   Mean; // weighted arithmetic mean
   double   D;    // variance
   double   SD;   // standard deviation
  };

sVseries-type variable allows you to receive all the main parameters of the variation series displayed as a histogram within a single call of the HistogramCharacteristics() function.

 

Method: DrawMean value visualization. 

Displays the value of the weighted arithmetic mean of the variation series on a chart. 

void DrawMean(
   double coord,     // weighted arithmetic mean value
   datetime time,    // current bar time
   bool marker=false,// show/hide the marker
   bool save=false   // save the value in history
   );

Parameters:

 coord

    [in] Weighted arithmetic mean value.

 time

    [in] Time of the current bar. The weighted arithmetic mean value is to be displayed on this bar.

 marker=false

    [in] Show/hide the chart marker. false — hide the marker, true — show the marker on a chart.

 save=false

    [in]  Save the weighted arithmetic mean value in history. false — hide the value, true — show the value on a chart.

Return value:

If successful, the chart displays a horizontal line corresponding to the weighted arithmetic mean value. 

 

Method: DrawSD standard deviation visualization.

Displays a standard deviation value as a rectangle with its width matching the average frequency and height matching the standard deviation plotted upwards and downwards from the weighted arithmetic mean value.

void DrawSD(
   sVseries &coord,        // sVseries-type variable
   datetime time,          // current bar time
   double deviation=1.0,   // deviation
   color clr=clrYellow     // display color
   );

Parameters:

 coord

    [in] sVseries-type variable value.

 time

    [in] Time of the current bar.

 deviation=1.0

    [in] Standard deviation increase ratio.

 clr=clrYellow

    [in] Color of the rectangle visualizing the standard deviation.

Return value

 If successful, the rectangle visualizing the standard deviation from the weighted arithmetic mean value is displayed on a chart.

  

Plotting histograms using the CHistogram class

Let's plot histograms of Bid and Ask statistical distributions, as well as ATR and MACD indicators (Fig. 9). Unlike the previous examples, this class allows plotting relative diagrams. Apart from the histogram, let's also create a graphical representation of a weighted arithmetic mean and standard deviation. The mean is to be saved using the indicator buffers. In the screenshot below, the standard deviations are displayed as rectangles with their width corresponding to the average frequency, while their height equal to the two deviations increased several times for better visibility. 

 Fig. 9. Histograms of iATR and iMACD indicators, as well as of Bid and Ask prices

Fig. 9. Histograms of iATR and iMACD indicators, as well as of Bid and Ask prices


The code of the example is attached below. The indicator buffers used in the program highlight the interaction of the class and the indicator functionality.

Conclusion

  • Statistical distributions of variation series in the form of histograms without indicator buffers and arrays can be implemented using the "graphical" memory.
  • When dealing with tasks involving graphical objects, it is often reasonable to apply graphical object properties as more resource-saving memory. Besides, we are able to access the extensive terminal and MQL5 language functionality, like sorting, grouping, search, sampling, direct access to the elements and others.
  • We have considered basic, primitive "graphical" memory application methods. In actual practice, it is possible to store small arrays and structures in the graphical object properties.

Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/2714

Last comments | Go to discussion (6)
Sergey Pavlov
Sergey Pavlov | 17 Jan 2017 at 02:42
cstangor:

Sergey, this is very interesting and I'd like to talk about it.

First, I don't know how to get the code from the news post.  I ended up saving the code and copy-pasting it from the saved file. I'm not sure what an .mht file is.

Anyway I pasted the  DrawHistogram() function but when I tried to compile it there were undefined variables. e.g color_R_active.  Can you help me define those.

Thank you.

Chuck Stangor 

Show the full code.

Charles Stangor
Charles Stangor | 17 Jan 2017 at 14:16
Sergey Pavlov:

Show the full code.

//+------------------------------------------------------------------+
//|                                               draw_histogram.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
// #define MacrosHello   "Hello, world!"
// #define MacrosYear    2010
//+------------------------------------------------------------------+
//| DLL imports                                                      |
//+------------------------------------------------------------------+
// #import "user32.dll"
//   int      SendMessageA(int hWnd,int Msg,int wParam,int lParam);
// #import "my_expert.dll"
//   int      ExpertRecalculate(int wParam,int lParam);
// #import
//+------------------------------------------------------------------+
//| EX5 imports                                                      |
//+------------------------------------------------------------------+
// #import "stdlib.ex5"
//   string ErrorDescription(int error_code);
// #import
//+------------------------------------------------------------------+

void DrawHistogram(bool draw,     // draw a histogram to the left or to the right
                   string h_name, // unique prefix for the object name
                   double price,  // price (analyzed parameter)
                   datetime time, // bind a histogram to the current bar
                   int span,      // analyzed parameter digit capacity
                   int swin=0)    // histogram window
  {
   double y=NormalizeDouble(price,span);
   string pfx=DoubleToString(y,span);
// if draw=true, draw the histogram to the right
   if(draw)
     {
      string name="+ "+h_name+pfx;                   // object name: prefix+price
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);    // create the object
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // set the object color
      ObjSet;                                                // code shortening macro
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {// if the resulting price entered the sample for the first time
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");          // the price freuqency is 1
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // define the time coordinate
        }
      else
        {// if the resulting price entered the sample not for the first time
         string str=ObjectGetString(0,name,OBJPROP_TEXT);    // get the property value
         string strint=StringSubstr(str,1);                  // highlight the substring
         long n=StringToInteger(strint);                     // get a frequency for further calculations
         n++;                                                // increase the value by 1
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // write a new value to the property
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//define the time coordinate
        }
     }
// if draw=false, write the histogram to the left
   if(!draw)
     {
      string name="- "+h_name+pfx;
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active);
      ObjSet;
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize);
        }
      else
        {
         string str=ObjectGetString(0,name,OBJPROP_TEXT);
         string strint=StringSubstr(str,1);
         long n=StringToInteger(strint);
         n++;
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n);
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n);
        }
     }
  ChartRedraw();
}
Sergey Pavlov
Sergey Pavlov | 17 Jan 2017 at 14:30
//+------------------------------------------------------------------+
//|                                               draw_histogram.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
//--- Макросы
#define  R        43    // значения префикса (+) для гистограмм справа
#define  L        45    // значения префикса (-) для гистограмм слева
#define  WIDTH    2     // толщина линий
#define  ObjSet1  ObjectSetInteger(0,name,OBJPROP_WIDTH,WIDTH)
#define  ObjSet2  ObjectSetDouble(0,name,OBJPROP_PRICE,0,y)
#define  ObjSet3  ObjectSetInteger(0,name,OBJPROP_TIME,0,time)
#define  ObjSet4  ObjectSetDouble(0,name,OBJPROP_PRICE,1,y)
#define  ObjSet5  ObjectSetInteger(0,name,OBJPROP_BACK,true)
#define  ObjSet   ObjSet1;ObjSet2;ObjSet3;ObjSet4;ObjSet5
//---
int      hsize=10;                     // масштаб гистограммы
color    color_R_active=clrRed;        // цвет активных линий справа
color    color_R_passive=clrLightCoral;// цвет пассивных линий справа
color    color_L_active=clrBlue;       // цвет активных линий слева
color    color_L_passive=clrSkyBlue;   // цвет пассивных линий слева

void DrawHistogram(bool draw,     // draw a histogram to the left or to the right
                   string h_name, // unique prefix for the object name
                   double price,  // price (analyzed parameter)
                   datetime time, // bind a histogram to the current bar
                   int span,      // analyzed parameter digit capacity
                   int swin=0)    // histogram window
  {
   double y=NormalizeDouble(price,span);
   string pfx=DoubleToString(y,span);
// if draw=true, draw the histogram to the right
   if(draw)
     {
      string name="+ "+h_name+pfx;                   // object name: prefix+price
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);    // create the object
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // set the object color
      ObjSet;                                                // code shortening macro
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {// if the resulting price entered the sample for the first time
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");          // the price freuqency is 1
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // define the time coordinate
        }
      else
        {// if the resulting price entered the sample not for the first time
         string str=ObjectGetString(0,name,OBJPROP_TEXT);    // get the property value
         string strint=StringSubstr(str,1);                  // highlight the substring
         long n=StringToInteger(strint);                     // get a frequency for further calculations
         n++;                                                // increase the value by 1
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // write a new value to the property
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//define the time coordinate
        }
     }
// if draw=false, write the histogram to the left
   if(!draw)
     {
      string name="- "+h_name+pfx;
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active);
      ObjSet;
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize);
        }
      else
        {
         string str=ObjectGetString(0,name,OBJPROP_TEXT);
         string strint=StringSubstr(str,1);
         long n=StringToInteger(strint);
         n++;
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n);
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n);
        }
     }
  ChartRedraw();
}
Charles Stangor
Charles Stangor | 17 Jan 2017 at 15:26
Sergey Pavlov:
//+------------------------------------------------------------------+
//|                                               draw_histogram.mqh |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
//--- Макросы
#define  R        43    // значения префикса (+) для гистограмм справа
#define  L        45    // значения префикса (-) для гистограмм слева
#define  WIDTH    2     // толщина линий
#define  ObjSet1  ObjectSetInteger(0,name,OBJPROP_WIDTH,WIDTH)
#define  ObjSet2  ObjectSetDouble(0,name,OBJPROP_PRICE,0,y)
#define  ObjSet3  ObjectSetInteger(0,name,OBJPROP_TIME,0,time)
#define  ObjSet4  ObjectSetDouble(0,name,OBJPROP_PRICE,1,y)
#define  ObjSet5  ObjectSetInteger(0,name,OBJPROP_BACK,true)
#define  ObjSet   ObjSet1;ObjSet2;ObjSet3;ObjSet4;ObjSet5
//---
int      hsize=10;                     // масштаб гистограммы
color    color_R_active=clrRed;        // цвет активных линий справа
color    color_R_passive=clrLightCoral;// цвет пассивных линий справа
color    color_L_active=clrBlue;       // цвет активных линий слева
color    color_L_passive=clrSkyBlue;   // цвет пассивных линий слева

void DrawHistogram(bool draw,     // draw a histogram to the left or to the right
                   string h_name, // unique prefix for the object name
                   double price,  // price (analyzed parameter)
                   datetime time, // bind a histogram to the current bar
                   int span,      // analyzed parameter digit capacity
                   int swin=0)    // histogram window
  {
   double y=NormalizeDouble(price,span);
   string pfx=DoubleToString(y,span);
// if draw=true, draw the histogram to the right
   if(draw)
     {
      string name="+ "+h_name+pfx;                   // object name: prefix+price
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);    // create the object
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_R_active); // set the object color
      ObjSet;                                                // code shortening macro
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {// if the resulting price entered the sample for the first time
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");          // the price freuqency is 1
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize); // define the time coordinate
        }
      else
        {// if the resulting price entered the sample not for the first time
         string str=ObjectGetString(0,name,OBJPROP_TEXT);    // get the property value
         string strint=StringSubstr(str,1);                  // highlight the substring
         long n=StringToInteger(strint);                     // get a frequency for further calculations
         n++;                                                // increase the value by 1
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n); // write a new value to the property
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time+hsize*n);//define the time coordinate
        }
     }
// if draw=false, write the histogram to the left
   if(!draw)
     {
      string name="- "+h_name+pfx;
      ObjectCreate(0,name,OBJ_TREND,swin,time,y);
      ObjectSetInteger(0,name,OBJPROP_COLOR,color_L_active);
      ObjSet;
      if(StringFind(ObjectGetString(0,name,OBJPROP_TEXT),"*",0)<0)
        {
         ObjectSetString(0,name,OBJPROP_TEXT,"*1");
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize);
        }
      else
        {
         string str=ObjectGetString(0,name,OBJPROP_TEXT);
         string strint=StringSubstr(str,1);
         long n=StringToInteger(strint);
         n++;
         ObjectSetString(0,name,OBJPROP_TEXT,"*"+(string)n);
         ObjectSetInteger(0,name,OBJPROP_TIME,1,time-hsize*n);
        }
     }
  ChartRedraw();
}
Thank you !
frcardim
frcardim | 30 Sep 2018 at 12:51

mr Sergey,

I´m trying plot tick buy and sell volume instead bid and ask to have a volume at price like indicator, is it possible ? do you have some code with tick buy/sell volume that can post ?

thank´s very much !


Example of data that I want to plot, it plots a horizontal histogram of buy/sell tick volume in separate window I want to plot a vertical histogram in main chart window :

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   2
//
#property indicator_label1  "SELL Tick"
#property indicator_type1   DRAW_HISTOGRAM
#property indicator_color1  clrTomato
#property indicator_style1  STYLE_SOLID
//
#property indicator_label2  "BUY Tick"
#property indicator_type2   DRAW_HISTOGRAM
#property indicator_color2  clrSpringGreen
#property indicator_style2  STYLE_SOLID
#property indicator_width2  5

//--- indicator buffers
double         ExtBuyBuffer[];
double         ExtSellBuffer[];

int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,ExtBuyBuffer,INDICATOR_DATA);
   SetIndexBuffer(1,ExtSellBuffer,INDICATOR_DATA);
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);
//---
   return(INIT_SUCCEEDED);
  }



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[])
  {

static int ticks=0;
//---
   if(ticks==0)
     {
      ArrayInitialize(ExtSellBuffer,0);
      ArrayInitialize(ExtBuyBuffer,0);
     }
//---
   MqlTick last_tick;
   if(SymbolInfoTick(Symbol(),last_tick) && last_tick.flags == 56)
     {
         ExtBuyBuffer[ticks]+=NormalizeDouble(last_tick.volume,2); 
         int shift=rates_total-1-ticks;
         ticks++;
         ExtBuyBuffer[rates_total-1]=last_tick.volume;
         PlotIndexSetInteger(0,PLOT_SHIFT,shift);
     }
   if(SymbolInfoTick(Symbol(),last_tick) && last_tick.flags == 88)
     {
         ExtSellBuffer[ticks]+=NormalizeDouble(last_tick.volume,2); 
         int shift=rates_total-1-ticks;
         ticks++;
         ExtSellBuffer[rates_total-1]=last_tick.volume;
         PlotIndexSetInteger(1,PLOT_SHIFT,shift);
     }

//
//--- return value of prev_calculated for next call

   return(rates_total);
 }

Patterns available when trading currency baskets Patterns available when trading currency baskets
Following up our previous article on the currency baskets trading principles, here we are going to analyze the patterns traders can detect. We will also consider the advantages and the drawbacks of each pattern and provide some recommendations on their use. The indicators based on Williams' oscillator will be used as analysis tools.
Graphical Interfaces X: Text Edit box, Picture Slider and simple controls (build 5) Graphical Interfaces X: Text Edit box, Picture Slider and simple controls (build 5)
This article will consider new controls: Text Edit box, Picture Slider, as well as additional simple controls: Text label and Picture. The library continues to grow, and in addition to introduction of new controls, the previously created ones are also being improved.
Graphical Interfaces X: Time control, List of checkboxes control and table sorting (build 6) Graphical Interfaces X: Time control, List of checkboxes control and table sorting (build 6)
Development of the library for creating graphical interfaces continues. The Time and List of checkboxes controls will be covered this time. In addition, the CTable class now provides the ability to sort data in ascending or descending order.
Universal Oscillator with a GUI Universal Oscillator with a GUI
The article describes the process of creation of a universal indicator based on all oscillators available in the terminal, with its own graphical interface. The GUI allows users to quickly and easily change settings of each oscillator straight from the chart window (without having to open its properties), as well as to compare their values and to select an optimal option for a specific task.