9. Price Data Set 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 several different types of investment price data. In this tutorial we will be dealing with daily data: the final values for the open, high, low and closing prices (OHLC data). We will not be dealing with intra-day tick data.

There are many places to download historical data from. For this tutorial I suggest using Yahoo Finance. Specifically, we will be using ten years of daily data on IBM.

Follow this link to load the Yahoo Finance historical data page for IBM.

Select the Time Period.

Set the start date to 01/01/2009.

Set the end date to 01/01/2019.

Select Done then select Apply.

Download the data. You should end up with a file called IBM.csv.

Don’t worry about the structure of the data file just yet. We will deal with that shortly. For now, it is important to note that our price data object will essentially be a vector of “a single day’s worth of data” class. We will call the latter the priceDatum class.

Add a new priceDatum class to your project.

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

Here is the header, priceDatum.h.


#ifndef PRICEDATUM_H
#define PRICEDATUM_H

// Class to store/retrieve one day's worth of OHLC data, used by priceDataSet class

class priceDatum
{
public:
    priceDatum(unsigned date, double open, double high, double low, double close);
    unsigned getDate();
    double getOpen();
    double getHigh();
    double getLow();
    double getClose();
private:
    unsigned date_;
    double open_;
    double high_;
    double low_;
    double close_;
};

#endif // PRICEDATUM_H

Here is the priceDatum.cpp source file.


#include "priceDatum.h"

// Class to store/retrieve one day's worth of OHLC data, used by priceDataSet class

priceDatum::priceDatum(unsigned date, double open, double high, double low, double close)
{
    date_ = date;
    open_ = open;
    high_ = high;
    low_ = low;
    close_ = close;
}

unsigned priceDatum::getDate()
{
    return date_;
}

double priceDatum::getOpen()
{
    return open_;
}

double priceDatum::getHigh()
{
    return high_;
}

double priceDatum::getLow()
{
    return low_;
}

double priceDatum::getClose()
{
    return close_;
}

Now we can create the class that will contain a vector of the above class along with the appropriate accessor functions.

Add a new priceDataSet class to your project.

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

Here is the header, priceDataSet.h


#ifndef PRICEDATASET_H
#define PRICEDATASET_H

#include <vector>
#include "priceDatum.h"

// Class to store / retrieve OHLC daily stock price data

class QIODevice;
class QString;

class priceDataSet
{
public:
    priceDataSet();
    ~priceDataSet();

    void test();
    bool Read(QIODevice *device);

    unsigned long getNumRows();
    unsigned getDate(unsigned long row);
    double getOpen(unsigned long row);
    double getHigh(unsigned long row);
    double getLow(unsigned long row);
    double getClose(unsigned long row);

private:
    std::vector<priceDatum> priceData_;
    unsigned getDateFromString(QString &);
};

#endif // PRICEDATASET_H

And here is the corresponding source file priceDataSet.cpp.

Pay particular attention to where the Read() function below calls the getDateFromString() function. This is where we leverage QT’s QDate class to map the input file to the appropriate fields in our data class. This is also where you would want to make some changes in order to read in a different date format.

Similarly, the local enum LINE_PARSE_INDEX defines the order of the csv data (eg., OHLC). This is where you would make changes to read a file with a different ordering or read in extra data fields like volume, adjusted closing price, etc.


#include <QDate>
#include <QTextStream>
#include <iostream>

#include "priceDataSet.h"

// Class to store / retrieve OHLC daily stock price data

priceDataSet::priceDataSet()
{
}

priceDataSet::~priceDataSet()
{
}

// Test / print all loaded data
void priceDataSet::test()
{
    // First check if we have some data
    if (getNumRows() > 1)
    {
        for (unsigned long i=0; i < getNumRows(); i++)
        {
            std::cout << getDate(i) << ","
                      << getOpen(i) << ","
                      << getHigh(i) << ","
                      << getLow(i) << ","
                      << getClose(i) << std::endl;
        }
    }
    else {
        std::cerr << "No data to print" << std::endl;
    }
}

// Convert string to unsigned in date
unsigned priceDataSet::getDateFromString(QString & inString)
{
    QDate date = QDate::fromString(inString,"yyyy-MM-dd");
    unsigned uDay = static_cast<unsigned>(date.day());
    unsigned uMonth = static_cast<unsigned>(date.month());
    unsigned uYear = static_cast<unsigned>(date.year());
    unsigned uDate = ((uYear*10000) + (uMonth*100) + (uDay));
    return uDate;
}


// Read a csv OHLC file from an open QT device
bool priceDataSet::Read(QIODevice *device)
{

    enum LINE_PARSE_INDEX
    {
        DATE = 0,
        OPEN = 1,
        HIGH = 2,
        LOW = 3,
        CLOSE = 4,
    };

    // Clear the gui
    int lineNum=0;

    // Create a thread to retrieve data from a file
    QTextStream in(device);

    //Read data up to the end of file
    while (!in.atEnd())
    {
        // Read in lines from the file
        QString line = in.readLine();
        if (lineNum > 1)
        {
            int dataIndex=0;
            unsigned date;
            double open,high,low,close,vol;

            date=0;
            open=high=low=close=vol=0.0;

            std::vector<double> priceData;

            for (QString datum : line.split(",")) {
                // First part of the line is the date
                if (dataIndex == DATE)
                {
                    date = getDateFromString(datum);
                }
                else if (dataIndex == OPEN)
                {
                    open = datum.toDouble();
                }
                else if (dataIndex == HIGH)
                {
                    high = datum.toDouble();
                }
                else if (dataIndex == LOW)
                {
                    low = datum.toDouble();
                }
                else if (dataIndex == CLOSE)
                {
                    close = datum.toDouble();
                }
                else
                {
                    // Do nothing, we're not using adj close or volume
                }
                dataIndex++;
            }

            // Create priceDatum element
            priceDatum newDataPoint = priceDatum(date,open,high,low,close);

            // Push element on to data vector
            priceData_.push_back(newDataPoint);
        }
        lineNum++;
    }

    device->close();
    return true;
}

// Get number of rows (days)
unsigned long priceDataSet::getNumRows()
{
    return priceData_.size();
}

// Get the date
unsigned priceDataSet::getDate(unsigned long row)
{
    return priceData_.at(row).getDate();
}

// Get the open
double priceDataSet::getOpen(unsigned long row)
{
    return priceData_.at(row).getOpen();
}

// Get the high
double priceDataSet::getHigh(unsigned long row)
{
    return priceData_.at(row).getHigh();
}

// Get the low
double priceDataSet::getLow(unsigned long row)
{
    return priceData_.at(row).getLow();
}

// Get the close
double priceDataSet::getClose(unsigned long row)
{
    return priceData_.at(row).getClose();
}

Build your your code and check for any errors. In the next section we will make some changes to the tradingModel class in order to load the data using the classes above


< previous | next >