11. Moving Average Class

Disclaimer

Before reading this tutorial you, the reader, agree to the following. This tutorial and the code it contains are designed to be informational and educational tools only. They do not constitute investment advice. The author, Dave Topper, strongly recommends that you seek the advice of a financial services professional before making any type of investment. This model is provided as a rough approximation of future financial performance. The results presented are hypothetical and will likely not reflect the actual growth of your own investments. The author, Dave Topper, is not responsible for any human or mechanical errors or omissions. The author, Dave Topper, is not responsible for the consequences of any decisions or actions taken in reliance upon or as a direct or indirect result of the information provided by these tools.

There are many different types of moving averages. The topic itself is quite interesting.

Click here to read more about the most common moving average types.

In this section we will be building a Simple Moving Average as defined by the Wikipedia link above. In short, we will be calculating the sum of the previous N data points (ie., closing prices) and dividing them by N. But rather than having to recalculate the entire sum for every new closing price we will implement the following method.

For a Simple Moving Average it is also possible to subtract the Nth data point from the sum and add the new data point, then repeat the division. This saves us N additions for every new value. As mentioned above, there are other types of moving averages which have slightly different properties. Some are even easier and faster to calculate. But since the Simple Moving Average is the most common, that’s what we will build.

This class will make use of Boost’s circular buffer class in order to keep the last N closing prices without incurring a significant performance hit.

Add a new simpleMovingAverage class to your project.

Be sure to enforce the camel case convention for the source and header file names.

This is the header file simpleMovingAverage.h.


#ifndef SIMPLEMOVINGAVERAGE_H
#define SIMPLEMOVINGAVERAGE_H

#include <boost/circular_buffer.hpp>
#include <memory>

// Semi efficient quick moving average class

class simpleMovingAverage
{
public:
    simpleMovingAverage(unsigned);

    void addDataPoint(double d);

    void addDataPoint(int i);

    double getCurrentValue();

    bool calcSimple();
    bool calcQuick();
    bool spoolSamples();

private:
    std::unique_ptr<boost::circular_buffer<double>> dataPoints_;
    unsigned mAvgLength_;
    unsigned numDataPointsRead_;
    double currentValue_;
};

#endif // SIMPLEMOVINGAVERAGE_H

This is the source file simpleMovingAverage.cpp.


#include "simpleMovingAverage.h"

// Semi-efficient quick moving average class

// Constructor
simpleMovingAverage::simpleMovingAverage(unsigned iLen)
{
    mAvgLength_ = iLen;
    if (iLen < 1)
    {
        iLen = 1;
    }
    dataPoints_ = std::unique_ptr<boost::circular_buffer<double>>(new boost::circular_buffer<double>(iLen));
    numDataPointsRead_ = 0;
    currentValue_ = 0.0;
}

// Add data points (double) and (int)
void simpleMovingAverage::addDataPoint(double iVal)
{
    // We haven't read mAvgLength_ worth of data yet, calculate using "slow" method
    if (spoolSamples())
    {
        dataPoints_->push_back(iVal);
        numDataPointsRead_++;
        if (calcSimple())
        {
            double sum = 0.0;
            for (unsigned i=0;i<mAvgLength_;i++)
            {
                double dataPoint = dataPoints_->at(i);
                sum += dataPoint;
            }
            currentValue_ = sum/mAvgLength_;
        }
    }

    // We have read mAvgLength_ worth of data, calculate via quick method
    else if (calcQuick())
    {
        // Calculate sum for current avg length
        double avgSum = currentValue_*mAvgLength_;
        // Subtract nth ago (0) element
        double nthElement = dataPoints_->at(0);
        avgSum -= nthElement;
        // Add latest value
        avgSum += iVal;
        // Re calculate the average
        currentValue_ = avgSum / mAvgLength_;
        // Add current value
        dataPoints_->push_back(iVal);
        numDataPointsRead_++;
    }
}

void simpleMovingAverage::addDataPoint(int iVal)
{
    double dVal = static_cast<double>(iVal);
    addDataPoint(dVal);
}

// Logic for calculating the average
bool simpleMovingAverage::calcSimple()
{
    return (numDataPointsRead_ == mAvgLength_);
}

bool simpleMovingAverage::calcQuick()
{
    return (numDataPointsRead_ >= mAvgLength_);
}

bool simpleMovingAverage::spoolSamples()
{
    return (numDataPointsRead_ < mAvgLength_);
}

// Return current value
double simpleMovingAverage::getCurrentValue()
{
    return currentValue_;
}

There are a few things to note about how this class works. First, as mentioned above, our data points are stored in a boost::circular buffer of type double. We keep N (the length of the moving average) samples in that buffer.

In order to add new values to the moving average call the function addDataPoint(). The class can take doubles or integers for convenience as they are the two most common numeric values you’ll find yourself using when designing trading models.

The addDataPoint method has two “modes.” Before N samples have been added to the moving average, the class simply stores values into the circular buffer. That code is contained inside the if (spoolSamples()) block. The currentValue_ of the moving average is kept at 0 during this time, as there is no actual valid value.

Once we have read more than N samples, we calculate the moving average as described at the beginning of this chapter: the calcQuick() code block. Note: when we have read exactly N samples, we do a one-time sum of all samples then divide (aka the “slow” method): the code is contained inside the calcSimple() block.

Add an include for the simpleMovingAverage header and modify the run() method in tradingModel.cpp to calculate two simple moving averages and print out their respective values along with the OHLC data.


// Run the model
void tradingModel::run()
{

    // Set up our moving averages
    std::unique_ptr<simpleMovingAverage> mAvg1 = 
std::unique_ptr<simpleMovingAverage>(new simpleMovingAverage(movingAvgLen1_));
    std::unique_ptr<simpleMovingAverage> mAvg2 = 
std::unique_ptr<simpleMovingAverage>(new simpleMovingAverage(movingAvgLen2_));

    double open,high,low,close;
    unsigned today;

     // MAIN TRADING ITERATOR
    for(unsigned long todayIndex = 0; todayIndex < priceData_->getNumRows(); todayIndex++)
    {
        // Grab price data into local variables
        today = priceData_->getDate(todayIndex);
        open = priceData_->getOpen(todayIndex);
        high = priceData_->getHigh(todayIndex);
        low = priceData_->getLow(todayIndex);
        close = priceData_->getClose(todayIndex);
        mAvg1->addDataPoint(close);
        mAvg2->addDataPoint(close);

        std::cout << today << ","
                  << open << ","
                  << high << ","
                  << low << ","
                  << close << ","
                  << mAvg1->getCurrentValue() << ","
                  << mAvg2->getCurrentValue() << std::endl;

    }
}

Compile and run your code. You should see the following output (last 10 lines):


20181217,119.07,119.78,115.07,116.1,120.662,125.628
20181218,116.9,118.23,116.02,116.65,120.416,124.98
20181219,117.15,120.27,115.97,116.43,120.222,124.341
20181220,115.7,116.45,111.7,113.02,120.013,123.657
20181221,112.5,115.28,110.44,110.94,119.632,123.022
20181224,109.9,111,107.5,107.57,119.151,122.393
20181226,108,111.39,105.94,111.39,118.742,121.803
20181227,109.99,113.78,109.47,113.78,118.43,121.256
20181228,114.22,114.8,112.5,113.03,117.931,120.615
20181231,113.33,114.35,112.42,113.67,117.541,120.207

So we now have output consisting of the IBM open, high, low, and close along with the corresponding values for a 20 and 50 day moving average respectively (assuming you’ve run the model with those two moving average values).

There is a subtle problem here. The moving average is being calculated to three decimal points. Calculations beyond two decimal points (ie., cents) can lead to rounding errors down the road. For example, assuming you end up calculating your share purchase price to three decimal placed, let’s say you buy ten thousand shares of a stock at $3.444 per share. Your total purchase price will be $34,440. That’s $40 more than purchasing ten thousand shares at $3.44 (ie., total purchase price of $34,400). This is just a very simple example. I find it useful to clamp prices to two decimal places as most stocks and virtually all stock options are priced as such.

In order to solve this problem we’ll implement a simple static function in the next section that rounds doubles to two decimal places.

< previous | next >