Poor man's Bollinger bands

ctodx
18 January 2010

Mehul's interview with Chris White (here) about EdgeRater was not only popular in the sense of seeing a successful stock market application written using DXperience, but also in the sense of the number of customers asking "when will XtraCharts support Bollinger bands" (EdgeRater uses Bollinger bands as the "edge" in making investment decisions). We are not currently planning to add Bollinger bands to XtraCharts this year, but until we do, here's a quick way to add them yourself.

Aside: For more information on Bollinger bands, see wikipedia's article or see John Bollinger's website.

In order to display Bollinger bands, we have some assumptions to make and some calculating to do. We assume that the data we're investigating are values over time. The archetypal example is of course stock prices, but it can be any measurement over time.

The first band is a moving average of the data over some number of periods, the width of the periods being known as the window. This is known as the middle band. For example, if we're measuring fuel efficiency for our car in miles per gallon per minute (that is, our data points are values in mpg, and we calculate it every minute by measuring how far we've gone and how much fuel we used in that minute), we may decide that our moving average is calculated from the last 20 data points, or 20 minutes. The number of is the first "knob" in our Bollinger band calculator engine; the bigger the value, the more it "smoothes out" the variability in the original data. Call the width of the window, that is, the number of periods we use in calculating the moving average, N.

The next two bands, known as the upper and lower bands, are calculated from the running standard deviations. In other words, over the same number of periods as we used for the moving average, we calculate the standard deviation each time. The upper band is calculated as some multiple (say 2) of the standard deviation over the average, and the lower band as the same multiple of the standard deviation under the average. The multiple is the second knob in the Bollinger band engine, although generally it's left at 2. Call this K. If the upper and lower bands are close together, it indicates low volatility in the original data; the wider apart they are, the greater the volatility. The significant points in the original data are those that lie above the upper band or below the lower band, and in a stock trading environment these are the points at which a trader might buy or sell the underlying stock.

To chart the Bollinger bands, we therefore have to calculate the moving average and standard deviations of the original set of data. To do this efficiently, we make use of three summation variables. The first is merely the count of data points in our window. Usually this is N, but for the first N-1 windows it will take on the values 1 to N-1. Commonly, we just ignore the first N-1 moving averages and start our bands at the Nth period; this is what we'll do here. The second is the sum of the data values in the window, S, and the third is the sum of the squares of the data values in the window, Q. The moving average is then (S / N), and the standard deviation is sqrt(N.Q - S^2) / N.

The nice things about using these running totals is that when we move the window along to the right by one period, we merely subtract the values at the point that slides out of the window on the left and add in the values for the point that slides in on the right. We do not have to calculate the entire total again from scratch every time. Also, it is this property that makes it ideal for calculating the bands for data that is constantly and frequently being added to.

To illustrate how to use Bollinger bands using XtraCharts, I created this small WinForms application. Drop a chart control onto a new form and dock it to fill the form. Don't bother setting any properties using the Properties pane, we'll be doing it all via code.

Change the form constructor to the following code. In it we shall load the data we're applying the Bollinger bands statistical analysis to (this will be a line series), and then calculate the bands themselves as area series.

    private int WindowSize = 20;
    private int SpreadSize = 2;

    public Form1() {
      InitializeComponent();
      LoadOriginalData();
      CalculateBands();

      var diagram = chartControl1.Diagram as XYDiagram;
      diagram.AxisX.DateTimeGridAlignment = DateTimeMeasurementUnit.Minute;
      diagram.AxisX.DateTimeMeasureUnit = DateTimeMeasurementUnit.Minute;
      diagram.AxisX.DateTimeOptions.Format = DateTimeFormat.ShortTime;
    }

We also make sure that the X axis displays times. (The WindowSize constant is what we called N, SpreadSize is K.)

The LoadOriginalData() method is pretty simple. I create a line series as a random set of points that has some random variability in the form of larger jumps, otherwise it just "jiggles" up and down.

    public double GetRandomValue(Random r, double oldValue) {
      double jump = 1.0;
      if (r.NextDouble() < 0.15)
        jump = (r.NextDouble() - 0.5) * (r.NextDouble() * 30.0);
      return (oldValue + jump) + ((r.NextDouble() - 0.6) * 5.0);
    }

    public void LoadOriginalData() {
      var series = new Series("OriginalData", ViewType.Line);
      series.ArgumentScaleType = ScaleType.DateTime;
      series.Label.Visible = false;
      var view = series.View as LineSeriesView;
      view.LineMarkerOptions.Visible = false;

      Random r = new Random();
      DateTime time = DateTime.Now;
      double value = 20.0;

      for (int i = 0; i < 200; i++) {
        time = time.AddMinutes(1);
        value = GetRandomValue(r, value);
        series.Points.Add(new SeriesPoint(time, value));
      }

      chartControl1.Series.Add(series);
    }

I also make sure to turn off the point markers and the labels: there are 200 points here and with markers/labels visible it would look a complete mess.

I then wrote a helper routine that would create a band as an area series, again turning off the labels and markers.

    private static Series GetBand(string name) {
      Series band = new Series(name, ViewType.Area);
      band.ArgumentScaleType = ScaleType.DateTime;
      band.Label.Visible = false;
      var view = band.View as AreaSeriesView;
      view.MarkerOptions.Visible = false;
      return band;
    }

The CalculateBands() method is next.

    public void CalculateBands() {
      SeriesPointCollection originalData = chartControl1.Series[0].Points;

      Series middleBand = GetBand("MiddleBand");
      Series upperBand = GetBand("UpperBand");
      Series lowerBand = GetBand("LowerBand");

      double sum = 0.0;
      double sumSquares = 0.0;

      SeriesPoint point;
      for (int index = 0; index < originalData.Count; index++) {
        if (index < WindowSize) {
          point = (SeriesPoint)originalData[index];
          sum += point.Values[0];
          sumSquares += Math.Pow(point.Values[0], 2.0);
        }
        else {
          point = (SeriesPoint)originalData[index - WindowSize];
          sum -= point.Values[0];
          sumSquares -= Math.Pow(point.Values[0], 2.0);

          point = (SeriesPoint)originalData[index];
          sum += point.Values[0];
          sumSquares += Math.Pow(point.Values[0], 2.0);

          double mean = sum / WindowSize;
          middleBand.Points.Add(new SeriesPoint(point.Argument, mean));
          double stdDev = Math.Sqrt(WindowSize * sumSquares - sum * sum) / WindowSize;
          upperBand.Points.Add(new SeriesPoint(point.Argument, mean + stdDev * SpreadSize));
          lowerBand.Points.Add(new SeriesPoint(point.Argument, mean - stdDev * SpreadSize));
        }
      }

      chartControl1.Series.Add(upperBand);
      chartControl1.Series.Add(middleBand);
      chartControl1.Series.Add(lowerBand);
    }

First of all we get a local copy of the original data series and create the three bands using my help routine. We initialize the running totals: sum and sumSquares.

Now for the fun bit: making a pass through the original data to calculate the moving average and standard deviation. The first 20 (actually WindowSize) points are merely used to "seed" the running totals; these will not be reflected in the Bollinger bands themselves. Once we get past the first 20 points we can start calculating the moving average and the standard deviation. As described in the text above, we drop off the oldest data point from the running totals, and add in the new data point. We can then calculate the mean and add it to the middle band. After that we calculate the running standard deviation using the formula above, from which we can add the upper spread point to the upper band and the lower one to the lower band.

Finally at the end of the method we add the new area series to the chart control, in order.

Running the application gives us this (click for full-sized image):

Bollinger Bands

Now, of course, this is still only a rough version of what might be achievable (for a start, the colors could be chosen better: these are the default ones), but as a first approximation it's a good rendition of displaying Bollinger bands with XtraCharts.

no comments
No Comments

Please login or register to post comments.