14. Putting It All Together

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.

We now have everything in place to run a trading model. Recall the basic execution sequence for our program.

  1. Create model params from command line args. We have essentially already done this.
  2. Create an instance of the trading model class.
  3. Initialize the model with the input params. Note: this will also load data from the specified input file.
  4. Run the model.
  5. Print out relevant statistics from the model run.

We implemented steps 1 through 3 several chapters ago. We have been building up some infrastructure to make step 4 and 5 usable and extendable.

Also recall the basic logic phases we will use to run our trading model.

  • A DECISION phase. This is where we would move from SOLD to BUY or BOUGHT to SELL.
  • An ACTION phase. This is where we would move from BUY to BOUGHT or SELL to SOLD.
  • An UPDATE phase where we update various components of our model that do not necessarily affect our Trade State.

In order to make everything work we will need to modify the trading model class destructor, init, run and printStats methods. We’ll finish printStats in the next section.

This is the final version of the header tradingModel.h.


#ifndef TRADINGMODEL_H
#define TRADINGMODEL_H

#include <memory>

#include "priceDataSet.h"
#include "stockPortfolio.h"

class modelParameters;

// Trading model class that executes trading logic for a crossover based strategy

// Enum for trade state machine
enum TRADE_STATE {
    SOLD = 0,
    SELL = 1,
    BOUGHT = 2,
    BUY = 3
};

// Class definition
class tradingModel
{
public:

    tradingModel();
    void test();

    // Steps 3-5 of the basic paradigm
    bool init(std::shared_ptr<modelParameters>);
    void run();
    void printStats();

private:

    // We will use these internally
    bool loadPriceData();
    void setTradeState(TRADE_STATE);
    TRADE_STATE getTradeState();

    TRADE_STATE tradeState_;

    std::shared_ptr<modelParameters> modelParams_;
    std::unique_ptr<priceDataSet> priceData_;

    unsigned movingAvgLen1_;
    unsigned movingAvgLen2_;

    std::unique_ptr<stockPortfolio> modelPortfolio_;

};

#endif // TRADINGMODEL_H

This is the “almost final” version of the source file tradingModel.cpp. We will finish printStats() in the next section.


#include <QFile>
#include <iostream>

#include "tradingModel.h"
#include "simpleMovingAverage.h"
#include "priceDataSet.h"
#include "stockPortfolio.h"
#include "tradingUtils.h"
#include "modelParameters.h"

// Trading model class that executes trading logic for a crossover based strategy

tradingModel::tradingModel()
{
}

void tradingModel::test()
{
    priceData_->test();
}

// Initialize the model
bool tradingModel::init(std::shared_ptr<modelParameters> inParams)
{
    // Start with a portfolio of $1,000
    double portfolioStartValue = 1000;
    modelPortfolio_ = std::unique_ptr<stockPortfolio>(new stockPortfolio(portfolioStartValue));

    // We start out as sold
    setTradeState(SOLD);

    // Grab input parameters and store them to local var modelParams_
    modelParams_ = inParams;

    // File name -> price data load
    bool priceDataLoaded = loadPriceData();

    // Moving average lengths
    movingAvgLen1_ = modelParams_->getMovingAvgLen1();
    movingAvgLen2_ = modelParams_->getMovingAvgLen2();

    bool movingAverageLengthsValid = ((movingAvgLen1_ > 0) && (movingAvgLen2_ > 0));

    return (priceDataLoaded && movingAverageLengthsValid);
}


// Read the data file and load it into a price data object
bool tradingModel::loadPriceData()
{
    bool fileReadSuccess = false;

    // Get the data filename
    QString priceDataFilename = modelParams_->getDataFilename();

    // Extra check to make sure we've received a file name
    if (priceDataFilename.isEmpty())
        return false;

    // Create new price data object
    priceData_ = std::unique_ptr<priceDataSet>(new priceDataSet());

    // Set up a QFile for reading
    QFile file(priceDataFilename);
    // Check if file exists
    if (file.exists())
    {
        // Check if the file is readable
        if ( !file.open(QFile::ReadOnly | QFile::Text) )
        {
            std::cerr << "Problem opening file " << priceDataFilename.toStdString() << std::endl;
            return false;
        }
        fileReadSuccess = priceData_->Read(&file);
    }
    return fileReadSuccess;
}


// Functions to manage our model's trade state (eg., state machine)
void tradingModel::setTradeState(TRADE_STATE inState)
{
    tradeState_ = inState;
}

TRADE_STATE tradingModel::getTradeState()
{
    return tradeState_;
}

// 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;

    double mAvg1Val = 0.0;
    double mAvg2Val = 0.0;

    // 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);

        // PHASE 1 - Decisions ------------------------------------------------

        // Buy signal
        if ((mAvg1Val > mAvg2Val) && (getTradeState() == SOLD))
        {
            setTradeState(BUY);
        }
        // Sell signal
        else if ((mAvg1Val < mAvg2Val) && (getTradeState() == BOUGHT))
        {
            setTradeState(SELL);
        }

        // PHASE 2 - Actions --------------------------------------------------

        // Execute buy signal
        if (getTradeState() == BUY)
        {
            std::cout << "BUY - ";
            std::cout << today << " ";
            double cashAvailable = modelPortfolio_->getCashValue();
            unsigned numSharesToBuy = static_cast<unsigned>(cashAvailable/close);
            std:: cout << numSharesToBuy << " shares at " << close << std::endl;

            modelPortfolio_->doBuy(close,numSharesToBuy);

            setTradeState(BOUGHT);
        }
        // Execute sell signal
        if (getTradeState() == SELL)
        {
            std::cout << "SELL - ";
            std::cout << today << " ";
            unsigned numSharesSold = modelPortfolio_->getNumShares();
            std:: cout << numSharesSold << " shares at " << close << std::endl;

            modelPortfolio_->doSell(close);

            setTradeState(SOLD);
        }

        // PHASE 3 - Updates / Calculations -----------------------------------

        // We're trading based on what happened "yesterday"
        // Update moving averages
        mAvg1->addDataPoint(close);
        mAvg2->addDataPoint(close);
        mAvg1Val = tradingUtils::fix2D(mAvg1->getCurrentValue());
        mAvg2Val = tradingUtils::fix2D(mAvg2->getCurrentValue());
        // Update portfolio value
        modelPortfolio_->update(close);

    }
}

// Print out various stats related to the most recent run
void tradingModel::printStats()
{
     // Implemented in section 15.
}

The main “work” is being done inside the commented “MAIN TRADING ITERATOR” so let’s take a look at that code


    // 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);

Here we’re setting up our data loop. We are iterating over all the days of data read from our input data file. For convenience we grab a few key values into local variables: today (the date as an unsigned int), open, high, low and close. NOTE: we aren’t using the open, high, and low in this model. We’ve only used it for printout just now. Many strategies and indicators require them, so I’ve left them in the file to illustrate the idea.


       // PHASE 1 - Decisions ------------------------------------------------

        // Buy signal
        if ((mAvg1Val > mAvg2Val) && (getTradeState() == SOLD))
        {
            setTradeState(BUY);
        }
        // Sell signal
        else if ((mAvg1Val < mAvg2Val) && (getTradeState() == BOUGHT))
        {
            setTradeState(SELL);
        }

Here is our first logic phase, decisions. Simply put, if the first moving average is greater than the second moving average and the current tradeState is SOLD, then set the tradeState to BUY. Similarly, if the first moving average is less than the second moving average and the current trade state is BOUGHT, then set the tradeState to SELL. Separating logic phases makes designing more complicated models much less confusion.


        // PHASE 2 - Actions --------------------------------------------------

        // Execute buy signal
        if (getTradeState() == BUY)
        {
            std::cout << "BUY - ";
            std::cout << today << " ";
            double cashAvailable = modelPortfolio_->getCashValue();
            unsigned numSharesToBuy = static_cast<unsigned>(cashAvailable/close);
            std:: cout << numSharesToBuy << " shares at " << close << std::endl;

            modelPortfolio_->doBuy(close,numSharesToBuy);

            setTradeState(BOUGHT);
        }
        // Execute sell signal
        if (getTradeState() == SELL)
        {
            std::cout << "SELL - ";
            std::cout << today << " ";
            unsigned numSharesSold = modelPortfolio_->getNumShares();
            std:: cout << numSharesSold << " shares at " << close << std::endl;

            modelPortfolio_->doSell(close);

            setTradeState(SOLD);
        }

Here is our second logic phase, actions. If we decided to BUY in the previous phase, we now execute it. This is done by first calculating how many shares we can buy, then calling the portfolio class doBuy method, then setting the trade state to BOUGHT. Similarly, if we decided to SELL in the previous phase, we now execute that. This is done by calling the portfolio class doSell method. As shown above, we’re also printing out some relevant information regarding the respective trades so that when we run the model we can see what happened and when.


        // PHASE 3 - Updates / Calculations -----------------------------------

        // We're trading based on what happened "yesterday"
        // Update moving averages
        mAvg1->addDataPoint(close);
        mAvg2->addDataPoint(close);
        mAvg1Val = tradingUtils::fix2D(mAvg1->getCurrentValue());
        mAvg2Val = tradingUtils::fix2D(mAvg2->getCurrentValue());
        // Update portfolio value
        modelPortfolio_->update(close);

In the last logic phase, we need to update key elements of our model. For a crossover strategy, there isn’t any real logic here. But it’s important to note what’s going on. We are setting up this crossover strategy to trade only when the moving averages close above or below each other. We are not dealing with finding the intra-day crossings of the two averages, although it would be possible to do so. We would essentially need to check if the moving averages cross using the open, high and/or low prices then calculate that crossover point. That would be another way of implementing the strategy. As we’ll see in the next few sections, there are many other conditions we can add to a basic crossover strategy.

Build the project and run the program. You should see the following output (last 10 lines).


BUY - 20160603 6 shares at 152.89
SELL - 20160914 6 shares at 154.05
BUY - 20161121 5 shares at 162.77
SELL - 20170330 5 shares at 173.86
BUY - 20170706 6 shares at 152.36
SELL - 20170725 6 shares at 146.19
BUY - 20170927 6 shares at 145.66
SELL - 20180222 6 shares at 153.18
BUY - 20180726 7 shares at 146.71
SELL - 20181022 7 shares at 130.02

In the next section we’ll implement the printStats method to establish some metrics for evaluating how our model performed over time. You may have noticed that some of the necessary code to do that has already been implemented in the last few chapters.

< previous | next >