Volatility analysis : bridging the gap from volatility forecasting to price forecasting

[Versiunea romaneasca] [MQLmagazine.com in romana] [English edition]

Volatility. A word that generates panic for investors as well as faster heartbeats for traders. How many times haven’t you tried before to trade only trending markets and catch the momentum ?

According to InvestorWords.com, volatility is the relative rate at which the price of a security moves up and down. Volatility is found by calculating the annualized standard deviation of daily change in price. If the price of a stock moves up and down rapidly over short time periods, it has high volatility. If the price almost never changes, it has low volatility. So volatility is measured by standard deviation (noted with greek lowercase sigma letter). And what’s the most popular indicator based on standard deviation ? You guessed, the famous Bollinger Bands. Bollinger Bands (BB) is one of the first indicators that you have learned to use in trading. You have been told that, there is a signal to buy or sell whenever the market touches one of the bands, followed by a possible walk along the band, and afterwards when price turns away, it will go like that until it touches the moving average. This rule actually doesn’t tell too much. There is nothing saying how much it will walk along the band until it turns back. Which you may find very frustrating on your own … equity. If you graph the basis of this indicator, the standard deviation, you will see a sinusoidal chart. Volatility goes up and down in quite a continuous pattern, without swift moves. Since it’s not making swift moves, it means the volatility itself is more predictible than the price.  Since it’s forecastable, why not using BBs over volatility ? But wait. Standard deviation is relative. It measures the dispersion over the analyzed period. If we add the BBs over the standard deviation, it means we will use the standard deviation of the standard deviation to catch moves in volatility! example1_bboverstd This time there is no fear of false positives, because the analysed data set, standard deviation of the price, is a continuous function. Second, even if the signal is wrong and the volatility turns back and trends down towards its moving average, there’s not too much damage done, because the volatility is already down when signal is taken, and a move down in volatility will mean less movement in price. The bluish line is the standard deviation of last 20 bars opening price. Signals we are interested in happen when volatility crosses its own upper BB. Of course, we are not interested in crosses with lower BB, because they mean volatility is going down faster than normal. During a trend, a powerful countermovement will appear like the volatility would be going down. Best signals are when crosses with the upper BB happen at a very reduced historical volatility – below 20% of the on-screen range. If you compare with the other signals on chart, BB over price intersections with the price, you can see that this one may cross even when the volatility is within its own BB bands, so this is a way to filter lots of error-prone BB signals that are given by price BB intersections. A good entry-exit criteria would be that the distance between the upper volatility BB band and the volatility to be below 2% of volatility range. Below is the MQL4 transcript of the Bollinger Bands over Standard Deviation indicator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//+------------------------------------------------------------------+
//|                                                    BBoverSTD.mq4 |
//|                                                 Bogdan Caramalac |
//|                                     mailto:fxeconomist@yahoo.com |
//+------------------------------------------------------------------+
#property copyright "Bogdan Caramalac"
#property link      "mailto:fxeconomist@yahoo.com"
 
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_color1 LightSeaGreen //STDDEV
#property indicator_color2 Blue          //MA
#property indicator_color3 Yellow        //Upper BB
#property indicator_color4 Yellow        //Lower BB
 
//---- input parameters
extern int       STDLEN=20;
extern int       BBLEN=10;
extern double    BBMULT=2.0;
extern string    MATYPE="SMA";
extern int       MALEN=0;
extern int       Logarithm=0;
 
double ExtMapBuffer1[];
double ExtMapBuffer2[];
double ExtMapBuffer3[];
double ExtMapBuffer4[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int init()
  {
//---- indicators
   SetIndexStyle(0,DRAW_LINE, 0, 1, indicator_color1);
   SetIndexBuffer(0, ExtMapBuffer1);
   SetIndexStyle(1,DRAW_LINE, 0, 1, indicator_color2);
   SetIndexBuffer(1, ExtMapBuffer2);
   SetIndexStyle(2,DRAW_LINE, 0, 1, indicator_color3);
   SetIndexBuffer(2, ExtMapBuffer3);
   SetIndexStyle(3,DRAW_LINE, 0, 1, indicator_color4);
   SetIndexBuffer(3, ExtMapBuffer4);
//----
   SetIndexDrawBegin(0,STDLEN);
 
   SetIndexDrawBegin(1,STDLEN+BBLEN-1);
   SetIndexDrawBegin(2,STDLEN+BBLEN-1);
   SetIndexDrawBegin(3,STDLEN+BBLEN-1);
   return(0);
  }
 
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int start()
   {
   if (Bars<=MathMax(STDLEN+BBLEN,MALEN))      
      return(0);    
   int pos=Bars-1;    
   ArraySetAsSeries(ExtMapBuffer1,true);    
   while (pos>=0)
     {
      if (MATYPE=="SMA")
        {
         ExtMapBuffer1[pos]=iStdDev(Symbol(),Period(),STDLEN,0,MODE_SMA,MODE_OPEN,pos);
         if (Logarithm==1&&ExtMapBuffer1[pos]!=0.00)
           ExtMapBuffer1[pos]=MathLog(ExtMapBuffer1[pos]);
        }
      if (MATYPE=="EMA")
        {
         ExtMapBuffer1[pos]=iStdDev(Symbol(),Period(),STDLEN,0,MODE_EMA,MODE_OPEN,pos);
         if (Logarithm==1&&ExtMapBuffer1[pos]!=0.00)
           ExtMapBuffer1[pos]=MathLog(ExtMapBuffer1[pos]);
        }
      if (MATYPE=="SMMA")
        {
         ExtMapBuffer1[pos]=iStdDev(Symbol(),Period(),STDLEN,0,MODE_SMMA,MODE_OPEN,pos);
         if (Logarithm==1&&ExtMapBuffer1[pos]!=0.00)
           ExtMapBuffer1[pos]=MathLog(ExtMapBuffer1[pos]);
        }
      if (MATYPE=="LWMA")
        {
         ExtMapBuffer1[pos]=iStdDev(Symbol(),Period(),STDLEN,0,MODE_LWMA,MODE_OPEN,pos);
         if (Logarithm==1&&ExtMapBuffer1[pos]!=0.00)
           ExtMapBuffer1[pos]=MathLog(ExtMapBuffer1[pos]);
        }
       pos--;
     }
   pos=Bars-1;
   while(pos>=0)
     {
      if (MATYPE=="SMA")
        {
         if (MALEN!=0)
           ExtMapBuffer2[pos]=iMAOnArray(ExtMapBuffer1,0,MALEN,0,MODE_SMA,pos);
         else
           ExtMapBuffer2[pos]=EMPTY_VALUE;
        }
      if (MATYPE=="EMA")
        {
         if (MALEN!=0)
           ExtMapBuffer2[pos]=iMAOnArray(ExtMapBuffer1,0,MALEN,0,MODE_EMA,pos);
         else
           ExtMapBuffer2[pos]=EMPTY_VALUE;
        }
      if (MATYPE=="SMMA")
        {
         if (MALEN!=0)
           ExtMapBuffer2[pos]=iMAOnArray(ExtMapBuffer1,0,MALEN,0,MODE_SMMA,pos);
         else
           ExtMapBuffer2[pos]=EMPTY_VALUE;
        }
      if (MATYPE=="LWMA")
        {
         if (MALEN!=0)
           ExtMapBuffer2[pos]=iMAOnArray(ExtMapBuffer1,0,MALEN,0,MODE_LWMA,pos);
         else
           ExtMapBuffer2[pos]=EMPTY_VALUE;
        }
       pos--;
     }
   pos=Bars-1;
   while(pos>=0)
     {
      ExtMapBuffer3[pos]=EMPTY_VALUE;
      ExtMapBuffer4[pos]=EMPTY_VALUE;
      ExtMapBuffer3[pos]=iBandsOnArray(ExtMapBuffer1,0,BBLEN,BBMULT,0,MODE_HIGH,pos);
      ExtMapBuffer4[pos]=iBandsOnArray(ExtMapBuffer1,0,BBLEN,BBMULT,0,MODE_LOW,pos);
      pos--;
     }
   return(0);
  }

Parameters:

STDLEN – length of the period whose standard deviation is calculated;

BBLEN – length of the period whose BB bands are calculated for;

BBMULT – standard deviation multiplicator for BB bands;

MATYPE – a helper Moving Average over the volatility is created, of this type ; same type is applied to the construction of the standard deviation in first place;

MALEN – period of the helper Moving Average (turned off with 0)

Logarithm – used for logarithming the scale ; for 0, disabled, for 1, natural logarithm;

Credits to Irtron and Ruptor from the mql4.com forum for sorting out this indicator.

The iMAOnArray function is buggy; an MQL4 indicator file can contain more than one loop used for calculation.

Let’s take a peek at the anatomy of this MQL4 indicator.

Lines 9 to 15 are the intro of the indicator ; it is specified that the indicator is in a separate window , number of buffers and coloring for each;

Lines 17 to 22 are the definition of parameters; what makes them parameters, as opposed to what is known as global variables in other programming languages , (for instance the Public declarations in Visual Basic) is the keyword extern in the front of the type ; when the indicator is installed (dragged over the chart) , these parameters can be modified . Also these parameters can be modified any time by the user ; but no expert advisor or script can touch the parameters of an indicator running in a window.

Lines 24 to 27 are the buffers ; since we specified that are 4 of them, 4 are defined. Indicator buffers are numbers that denominate arrays inside indicators that contain data. For instance, if an indicator would copy OHLC bar data to indicator buffers, 0 would be for open prices, 1 for high , 2 for low, 3 for close. In our case, as you will see later, 0 is for standard deviation, 1 for helper moving average, 2 for higher BB band and 3 for lower BB band.

Lines 31 to 49 make up the init() function. This function is called every time the chart parameters are changed (symbol , periodicity) or connection coming back up. The function defines the drawing style for each of the buffers and assigns the arrays to indicator buffers. Also specifies where drawing point begins for each buffer. Drawing point should be carefully calculated, so that data that is the base for buffer to be there previously. For instance, if you calculate a 10 element moving average, drawing point should not be larger than 10 elements below the count of bars.

Lines 54 to 131 make up the start() function. start() is triggered at every incoming quote. It starts with a condition checking that there are enough Bars to draw the indicator. Then, sets the position to start drawing. Position is a bar index. Indicator is calculated from a positive position to zero (current bar).  The ArraySetAsSeries() function alters the working mode of an array, setting it work like time series. This does not affect the array per se. Rather, it tells the special functions that work with arrays to treat arrays reversed. The functions that calculate indicators over arrays (like iMAOnArray()iStdDevOnArray() and so on) pass the array from left to right, from lower indexes to higher indexes. The ArraySetAsSeries() applied to an array will make these functions pass the array from right to left, from higher indexes to lower indexes, exactly how a custom indicator loop does. Then what follows are the loops. In a simple indicator quite everything can be done in a single loop, but here three loops do the trick. The first loop will calculate the standard deviation ; the second loop will calculate the helper moving average, and finally the third loop will calculate the upper and lower BBs.

Same indicator, MQL5 version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//+------------------------------------------------------------------+
//|                                                    BBoverSTD.mq5 |
//|                                       Copyright Bogdan Caramalac |
//|                                                  mqlmagazine.com |
//+------------------------------------------------------------------+
#property copyright "Bogdan Caramalac"
#property link      "mqlmagazine.com"
#property version   "1.02"
 
#property indicator_separate_window
#property indicator_buffers 4
#property indicator_applied_price PRICE_OPEN
 
#property indicator_plots   4
#property indicator_type1 DRAW_LINE
#property indicator_label1 "Stddev"
#property indicator_color1 LightSeaGreen //STDDEV
#property indicator_type2 DRAW_LINE
#property indicator_label2 "Helper MA"
#property indicator_color2 Blue          //MA
#property indicator_type3 DRAW_LINE
#property indicator_label3 "Upper BB"
#property indicator_color3 Yellow        //Upper BB
#property indicator_type4 DRAW_LINE
#property indicator_label4 "Lower BB"
#property indicator_color4 Yellow        //Lower BB
 
//---- input parameters
input int       STDLEN=20;
input int       BBLEN=10;
input double    BBMULT=2.0;
input string    MATYPE="SMA";
input int       MALEN=2;
input int       Logarithm=0;
 
double ExtMapBuffer1[];
double ExtMapBuffer2[];
double ExtMapBuffer3[];
double ExtMapBuffer4[];
 
#include <MovingAverages.mqh>
 
int StdDevHandle;
 
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0, ExtMapBuffer1);
   SetIndexBuffer(1, ExtMapBuffer2);
   SetIndexBuffer(2, ExtMapBuffer3);
   SetIndexBuffer(3, ExtMapBuffer4);
//----
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,STDLEN);
   PlotIndexSetInteger(1,PLOT_DRAW_BEGIN,STDLEN+BBLEN-1);
   PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,STDLEN+BBLEN-1);
   PlotIndexSetInteger(3,PLOT_DRAW_BEGIN,STDLEN+BBLEN-1);
//---
   //we create iStdDev handler for use in main cycle;
   if (MATYPE=="SMA")
     StdDevHandle=iStdDev(Symbol(),Period(),STDLEN,0,MODE_SMA,PRICE_OPEN);
   if (MATYPE=="EMA")
     StdDevHandle=iStdDev(Symbol(),Period(),STDLEN,0,MODE_EMA,PRICE_OPEN); 
   if (MATYPE=="LWMA")
     StdDevHandle=iStdDev(Symbol(),Period(),STDLEN,0,MODE_LWMA,PRICE_OPEN); 
   if (MATYPE=="SMMA")
     StdDevHandle=iStdDev(Symbol(),Period(),STDLEN,0,MODE_SMMA,PRICE_OPEN);  
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
   {
    double res[]; //array to hold current iStdDev value;
    double buffer_helper[500];
    double stddevonstddev=0.0;    //current stddev on stddev
    double squaredsum=0.0;        //current squared sum
    double bbma=0.0;              //current ma for bollinger bands calculus
    double bbsum=0.0;             //current sum for calculus of bbma
    double prevma=0.0;            //previous helper moving average value, for use with SMMA or LWMA   
    int buff_stddevs=0;           //used length of stddevs buffer
    int buff_helper_len=0;        //used length of helper buffer   
    int startpos=MathRound(MathMin(prev_calculated,rates_total-1)); //this is the start position ; it goes UPWARD, we are on an array, not on series
    if (rates_total<STDLEN)
      return(rates_total);
    if (startpos<STDLEN)
      startpos=STDLEN;
    for (int pos=startpos;pos<rates_total;pos++) //main cycle, going from startpos to rates_total
       {
        //Reads 1 element from position rates_total-pos, buffer 0 of StdDevIndicator calculus designated by StdDevHandle
        //rates_total-pos to convert position ; buffers are time series (curent value has index 0)
        CopyBuffer(StdDevHandle,0,rates_total-pos,1,res);
        ExtMapBuffer1[pos]=res[0];//copy value from temporary res[0] array index to final ExtMapBuffer1        
        if (Logarithm==1)
          ExtMapBuffer1[pos]=MathLog(ExtMapBuffer1[pos]);
        if (Logarithm==2)
          ExtMapBuffer1[pos]=MathLog10(ExtMapBuffer1[pos]);
        if (pos>STDLEN+BBLEN)
          {
           //enough values to calculate Bollies over volatility
           squaredsum=0.0;
           bbsum=0.0;
           //calculating the moving average for bollinger bands
           for (int j=pos-BBLEN+1;j<=pos;j++)
              bbsum=bbsum+ExtMapBuffer1[j];
           bbma=bbsum/BBLEN;
           //calculating the squared sum for the stddev over stddev needed for bollinger bands
           for (int j=pos-BBLEN+1;j<=pos;j++)
              squaredsum=squaredsum+(ExtMapBuffer1[j]-bbma)*(ExtMapBuffer1[j]-bbma);
           //stddev on stddev and bollinger bands
           stddevonstddev=MathSqrt(squaredsum/BBLEN);
           ExtMapBuffer3[pos]=bbma+BBMULT*stddevonstddev;
           ExtMapBuffer4[pos]=bbma-BBMULT*stddevonstddev;
           if (pos>MALEN+BBLEN+STDLEN)
             {
              if (MALEN!=0) //goes in only if helper moving average is indeed enabled
                {
                 //making buffer for helper MA
                 for (int j=1;j<=MALEN;j++)    
                    buffer_helper[MALEN-j+1]=ExtMapBuffer1[pos-j+1];            
                 //calculating different kinds of moving averages, by calling moving average methods from MovingAverages.mqh
                 if (MATYPE=="SMA")
                   ExtMapBuffer2[pos]=SimpleMA(MALEN,MALEN,buffer_helper);
                 if (MATYPE=="EMA")
                   {
                    ExtMapBuffer2[pos]=ExponentialMA(MALEN,MALEN,prevma,buffer_helper);
                    prevma=ExtMapBuffer2[pos];
                   }
                 if (MATYPE=="SMMA")
                   {
                    ExtMapBuffer2[pos]=SmoothedMA(MALEN,MALEN,prevma,buffer_helper);
                    prevma=ExtMapBuffer2[pos];
                   }
                 if (MATYPE=="LWMA")
                   ExtMapBuffer2[pos]=LinearWeightedMA(MALEN,MALEN,buffer_helper);      
                }//if (MALEN!=0)
             }//if (pos>MALEN+STDLEN)                           
          }//else if if (buff_stddevs<BBLEN)
       }//for (pos=startpos;pos<rates_total;pos++)
   return(rates_total);
  }

Well, programming in MQL5 is signifiantly harder. MQL5 has a whole different way of approaching indicators, so different that comparisons to MQL4 are practically pointless. The indicator begins in a similar manner, with a larger intro, lines 10-26 . The intro has a much wider number of settings compared to MQL4. Then follow the parameters, lines 29-34, and finally the buffers, lines 36-39. Note in lines 29-34 the replacement of extern by input , which takes its place in MQL5. extern still remains valid in MQL5, but it has a different role.

Until this moment it doesn’t seem too complicated, because code is similar to MQL4. But now it takes a complete different turn…
First, an include of the MovingAverages.mqh on line 41. This will be needed below, because the different moving averages calculations will be done by calling moving average routines residing in this lib. Then, on line 43, public declaration of Standard Deviation indicator handle (only one, because we pick by moving average type). In MQL5, calls to indicators don’t return indicator values, as they were doing in MQL4. Rather, they return a handle. The handle is needed later to get indicator values, in a calls to CopyBuffer(). The OnInit(), spanning on lines 48-71, is similar again to MQL4 init() , at least in the 50-59 lines , where there are buffer assignations and draw begin setups. The OnInit() ends with calls to iStdDev() ; the indicator will be used inside the main loop, but the handle is generated once! Four calls to iStdDev, generating the unique handle for the chosen type of moving average required by MATYPE parameter.

The OnCalculate() event is practically the new master loop. This function has a kind of a mysterious prototype. Its implementation details are not fully known, so the function allows a pretty wide range of prototypes. The one I used in the indicator is the default one that you get when you create a new indicator. As opposed to MQL4, when index bars are descendant from Bars to 0, current bar, in MQL5, index bars are ascendant from prev_calculated-1 to rates_total-1.

Following the declarations of the variables, the loop begins in line 100. First thing to do, we get the current value of the standard deviation. Since we have the handler, we call the CopyBuffer() to retrive the value for the current position. The current position is the cycle variable pos. However, CopyBuffer() understands position as a position in a series, not as a position in an array. Therefore, we will copy 1 value from rates-total-pos, from the first series, 0. If the indicator would have returned more than one value, say the indicator would have returned more value, like a Heiken Ashi, for instance, that returns bar informations, we would have had more than one series, adressing series 0, 1, 2 and 3 to get values of Heiken Ashi bar prices. The values got with CopyBuffer() are put in an array. But we need one number, so the array where we put the data , res will have only one value, res[0], which is copied to our first buffer, ExtIndexBuffer1, and then logarithming is applied, according to Logarithm parameter, lines 106-109 (now the decimal logarithm is included too, besides the natural).

What follows is pretty easy to follow. We count if we have enough values to start calculating the Bollinger Bands over the standard deviation. When there is enough data , (“if” branch line 110), we go back BBLEN values and calculate the average, then we do again same loop and calculate the standard deviation over the standard deviation, then finally Bollinger Bands , lines 124-125.
Same as before, but with the condition of having at least one standard deviation over standard deviation calculated, we check to see if we have MALEN values of these, to compute the first helper moving average. But unlike previous time, we copy values of initial standard deviation in a buffer. Buffer will span from MALEN down to 1, with larger indexes for older values. The reason to do it is that we will pass this buffer as a parameter to moving average methods taken from MovingAverages.mqh, and their inner implementation is passing this way thru the arrays. These are simple functions. Unlike indicators, their results are answers of inner calculations, not handlers. And their results are written in the buffer for the helper moving average, ExtMapBuffer2. With the calculation of the helper moving average, all calculations are done and the loop closes.

Volatility forecasting

This is why I included in the indicator a helper moving average: to help forecast volatility. I would stress, however, that some key requirements to be met before attempting to do so:
1. The BB band channel should be below  2% from historical standard deviation range – this will insure that the signal does not happen accidentally when BB bands are already at distance.
2. Current standard deviation should be below 5% from historical standard deviation high – this will insure that there is a high chance of an explosive volatility growth after the signal.

The normal signal should be the cross of the upper BB band. The exit signal should be determining a weakening in volatility speed. And that happens when volatility tops while BB upper band still going up. To determine this quick, use a quick helper moving average – with a period of 2 . Once that crosses the volatility, you can already consider the exit signal. But it can be determined even quicker, when there is a too large distance between the higher BB band and the standard deviation. One of the best interpretations of this chart would be by converting data – BB bands, standard deviation, moving average etc. to their graphics counterparts – point coordinates on chart. This might tell you for instance when the standard deviation touches the upper BB band.

Provided that you calculate the upper and lower BB for a fixed number of bars maybe WindowBarsPerChart() that gives the number of bars of the chart where your expert advisor or script runs, or a given number of bars – and you extract the highest upper BB and lowest lower BB, let’s name them bbmax and bbmin – you can calculate a virtual graphics position of any value (be it a BB, standard deviation or helper moving average), using a given screen height (say 300 virtual “pixels”). Let’s call this one testvalue. So the position of the testvalue would be:

double position=MathRound( height*(testvalue-bbmin)/bbmax );

This way, a composite condition would be, after conditions 1. and 2. are fulfilled, that there is either a cross or a touch. Exit criteria would be : standard deviation is below upper BB and there is no touch anymore. However, at the first sign of weaking volatility, the exit signal will be triggered, and that might happen too early. It would be good to backcheck the touch status using a lower resolution.

There are two possibly situations that contribute to standard deviation growth: either prices are going either up or down in an accelerated fashion, or prices change direction swiftly from up to down and viceversa with a growing amplitude.

If the volatility kept going up for the last bars , we might presume first condition : that prices are trending. In this case, we can just check the last 2-3 opening prices to see the direction and trade it, if it’s established. But, no matter if direction is established or not, the volatility can still go up. Because volatility is at historically low when we take the signal, and tiny increases mean just the aggitation is growing up, not that the direction is established. Price fluctuations may follow an up-down pattern with growing amplitude, until direction becomes established. However, volatility is still low. Being low, it means that prices don’t move too much – we can afford to lose. No matter which direction we trade, we check at every new bar if we trade it in the good direction. If not, cut loss and trade again. Because if the signal is correct and volatility is going to traverse 30% to 70% of its historical range, we’re in for big bucks!

Volatility forecast is pretty straightforward, and is more manual than automated. Since we expect volatility to be going thru the roof, we can do a linear or parabolic forecast.  The linear model is simple. If we have the current standard deviation on bar 0 (s0) and the expected standard deviation for bar m (sm), we have forecasted standard deviation per bar f(bar)=so+(sm-so)*bar/m.

The parabolic model is harder, because it needs an extra point to define the curvature. This is solvable with an equation system. Given three points (b1,stddev1) , (b2,stddev2) , (b3,stddev3) where b1, b2, b3 are bar indexes and stddev1,stddev2,stddev3 are estimated standard deviations :
three points quadratic function

Once we have the parameters of the quadratic function that describes the standard deviation according to estimated points, we can generate the standard deviation per each bar, see MQL4 script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
double Determinant(double &origa[][],int l1,int c1,int l2,int c2,int jumpline,int jumpcol)
    {
    double a[50,50];
    int degree,i,j,ip,jp,dets;
    double result=0;
    double reshere=0;
    double r;
    string image;
    int ipm,jpm;
    for (i=0;i<50;i++)
       {
       for (j=0;j<50;j++)
          a[i,j]=0.0;
       }
    if (l1==0)
       l1=1;
    if (c1==0)
       c1=1;
    degree=c2-c1+1;
    if (jumpcol!=0)
       degree=degree-1;
    ip=1;
    for (i=l1;i<=l2;i++)
      {
      ipm=ip-1;       
      jp=1;
      if (i!=jumpline)
        {
        for (j=c1;j<=c2;j++)
          {
          jpm=jp-1;
          if (j!=jumpcol)
             {
             r=origa[i-1,j-1]*1.0;                          
             a[ipm,jpm]=r;             
             jp=jp+1;
             }
          }//for (j=c1;j<=c2;j++c2)              
        ip=ip+1;
        }//if (i!=jumpline)
      }//for (i=l1;i<=l2;i++)
    result=0;
    if (degree==1)
       result=0;  
    if (degree==2)
       result=a[1-1,1-1]*a[2-1,2-1]-a[2-1,1-1]*a[1-1,2-1];
    if (degree==3)
       result=a[1-1,1-1] * a[2-1,2-1] * a[3-1,3-1] + 
              a[1-1,2-1] * a[2-1,3-1] * a[3-1,1-1] + 
              a[1-1,3-1] * a[2-1,1-1] * a[3-1,2-1] - 
              a[1-1,3-1] * a[2-1,2-1] * a[3-1,1-1] - 
              a[1-1,2-1] * a[2-1,1-1] * a[3-1,3-1] - 
              a[1-1,1-1] * a[2-1,3-1] * a[3-1,2-1] ;    
    if (degree>3)
       {
       for (dets=1;dets<=degree;dets++)
           {
           reshere=Determinant(origa,1,1,degree,degree,1,dets);
           result=result + MathPow(-1,1+dets)*origa[1-1,dets-1]*reshere;
           }
       }
    return(result);
    }
 
void EstablishStddevFunction(int b1,double stddev1,int b2,double stddev2,int b3,double stddev3,double &a, double &b, double &c)
  {
   double d,da,db,dc;
   double barray[3];
   double stddev[3];
   double det_d[3][3];
   double det_da[3][3];
   double det_db[3][3];
   double det_dc[3][3];
   barray[0]=b1*1.0;
   barray[1]=b2*1.0;
   barray[2]=b3*1.0;
   stddev[0]=stddev1;
   stddev[1]=stddev2;
   stddev[2]=stddev3;
   det_d[0][0]=barray[0]*barray[0];  det_d[0][1]=barray[0];   det_d[0][2]=1;   
   det_d[1][0]=barray[1]*barray[1];  det_d[1][1]=barray[1];   det_d[1][2]=1;
   det_d[2][0]=barray[2]*barray[2];  det_d[2][1]=barray[2];   det_d[2][2]=1;
 
   det_da[0][0]=stddev[0];  det_da[0][1]=barray[0];   det_da[0][2]=1;   
   det_da[1][0]=stddev[1];  det_da[1][1]=barray[1];   det_da[1][2]=1;
   det_da[2][0]=stddev[2];  det_da[2][1]=barray[2];   det_da[2][2]=1;
 
   det_db[0][0]=barray[0]*barray[0];  det_db[0][1]=stddev[0];   det_db[0][2]=1;   
   det_db[1][0]=barray[1]*barray[1];  det_db[1][1]=stddev[1];   det_db[1][2]=1;
   det_db[2][0]=barray[2]*barray[2];  det_db[2][1]=stddev[2];   det_db[2][2]=1;
 
   det_dc[0][0]=barray[0]*barray[0];  det_dc[0][1]=barray[0];   det_dc[0][2]=stddev[0];   
   det_dc[1][0]=barray[1]*barray[1];  det_dc[1][1]=barray[1];   det_dc[1][2]=stddev[1];
   det_dc[2][0]=barray[2]*barray[2];  det_dc[2][1]=barray[2];   det_dc[2][2]=stddev[2];
 
   d=Determinant(det_d,1,1,3,3,0,0);
   da=Determinant(det_da,1,1,3,3,0,0);
   db=Determinant(det_db,1,1,3,3,0,0);
   dc=Determinant(det_dc,1,1,3,3,0,0);   
   a=da/d;
   b=db/d;
   c=dc/d;
  }
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
   double aa,bb,cc;
   Print("Standard Deviation parabolic forecasting example");        
   EstablishStddevFunction(0,0.003,3,0.006,9,0.1,aa,bb,cc);
   Print("Forecasted standard deviations");
   for (int bar=0;bar<10;bar++)
      {
       Print("bar ",DoubleToStr(bar,0),"  stddev=",DoubleToStr(aa*bar*bar+bb*bar+cc,4));
      }
   return(0);
  }
//+------------------------------------------------------------------+

Note that the Determinant function has a recursive application, up to the degree 3, where Sarrus rule is applied. Thus you can use this function to calculate n-degree determinants – but be careful on time and memory allocation requirements. An MQL5 prototype of this function will look like this:

double Determinant(double &origa[][50],ushort l1,ushort c1,ushort l2,ushort c2,ushort jumpline,ushort jumpcol)

Note that the second dimension is fixed at 50. MQL5 does not support unspecified dimensions larger than one.

All of these might sound good, but don’t forget that we don’t trade volatility. We don’t have a price direction signal.

But the price direction can be extracted from volatility evolution.

Price forecasting from volatility forecast

We have to consider that at every new bar, a price goes out of analysis and another price comes in. If we have oldest for oldest price and newest for the incoming price, we have:

New Average = Old Average – Oldest member/N + Newest member/N ;

Now, given that we plotted already the following standard deviations, the question is, which is the newest price that will give the new standard deviation in the queue?

So we take the estimated new standard deviation, according to the model, and we square it : thus we have the variance. Multiplying the variance by N (number of prices in the analysis), we start shaping up the equation for the newest member.  Taking into account n elements, from P1 (the element thrown out) to Pn (the last element), Pn+1 (the new price that has to be determined) and the current average, we form the standard deviation equation, by squaring the requested standard deviation and multiplying it by n, which yields the sum of squares Pi – new average , which is computed as above, cutting out the P1 influence and adding Pn+1 influence. We use as helper a variable called avgc , which is the average less the influence of P1: (Needless to say, equation is written for the case standard deviation is calculated using a regular simple moving average)

stddev equation

So what you can see is what it was expected. Every standard deviation appears as a result of a new price, which can be either up, or down, which are, of course, roots a quadratic equation. I grouped the elements in order to see the coefficients for free Pn+1, squared Pn+1 and free member. Given m forecasted standard deviations, we have 2^m forecasted prices final after m bars. Of course, these are not to be generated straight ; they form a tree ; so first calculation will yield two prices ; every one of them will enter the price based for a new calculation and so on. So we will have 2, 4, 8, 16…2^m prices until the end. Most likely, the price will not have an amplified ranging movement for all the m bars. Rather, we can expect a few bars to be like that, after that the direction will be established. It is good to keep the high and low prices per each calculated bar. We can be whipsawed at the beginning, during a few swings – and here the estimations will be useful to calculate possible losses – but after these swings price movements will be way larger than in the swing period. My advice is that this method to be used on longer timeframes – larger than four hours. It can work on shorter timeframes, but big volatility catches on longer timeframes can yield spectacular results.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void CalculateStats(double &data[],int lastindex,double &avg, double &stddev)
  {
   double sum=0;
   int i;
   for (i=0;i<=lastindex;i++)
      {
       sum=sum+data[i];
      }
   avg=sum/(lastindex+1);
   sum=0;
   for (i=0;i<=lastindex;i++)
      {
       sum=sum+(data[i]-avg)*(data[i]-avg);
      }
   stddev=MathSqrt(sum/(lastindex+1));
  }
 
void CalculateNextPrices(double &p[],int lastindex,double newstddev,double &newprice1,double &newprice2)
  {
   double avg,avgc,stddev,suma2pi,sumapi2,sumapimavgc,sumapimavgc2,delta,a,b,c;
   int n,i;
   CalculateStats(p,lastindex,avg,stddev);
   n=lastindex+1;
   avgc=avg-p[0]/n;
   for (i=1;i<=lastindex;i++)
     {
      suma2pi=suma2pi+(2*p[i]);
      sumapi2=sumapi2+(p[i]*p[i]);
      sumapimavgc=sumapimavgc+(p[i]-avgc);
      sumapimavgc2=sumapimavgc2+(p[i]-avgc)*(p[i]-avgc);
     }
   a=1-(1.0/n);
   b=(2.0*avgc/n)-(2.0/n)*sumapimavgc-2.0*avgc;
   c=sumapimavgc2+avgc*avgc-newstddev*newstddev*n;
 
   delta=b*b-4.0*a*c;
   newprice1=(-b-MathSqrt(delta))/(2.0*a);
   newprice2=(-b+MathSqrt(delta))/(2.0*a);
  }

The first function calculates the statistical parameters : average and standard deviation. The second function calculates the new prices that match up the new standard deviation. Both arrays are zero-based. The lastindex parameter is the last subscript that contains data. Note the use of integer constants in formulas that calculate double variables. These constants must be written in a double fashion : 1.0 instead of 1, 2.0 instead of 2, and so on , otherwise results are error-prone.

Be careful when using parabollic standard deviation calculus. If the first chosen standard deviation is before parabola peak, the standard deviation got from the function may get to zero or negative, where real roots of the price quadratic function will be one or none – complex roots don’t have any meaning here!

Volatility test

3 Responses to “ Volatility analysis : bridging the gap from volatility forecasting to price forecasting ”

  1. Bogdan Caramalac, MQLmagazine sr.editor on December 1, 2009 at 11:01 am

    Corrected an error affecting the MQL4 version of the indicator, on helper moving average calculus: replaced BBLEN with MALEN on lines 94,101,108 and 115.

  2. Jonathon on January 9, 2010 at 8:04 pm

    Nice analysis Bogdan! Really good work

  3. Bogdan Caramalac, MQLmagazine sr.editor on May 19, 2010 at 3:30 pm

    Corrected MQL5 version of indicator to make it work under Strategy Tester.
    Modified MQL4 version of indicator, switched BBMULT type to double.