Entrada destacada

OHLC time series in Quantlib

Below is a demo code creating a QuantLib (‘QL‘, thereafter) object with OHLC (Open, High, Low, Close) data extracted from a standard *.csv file.

QL provides the QuantLib::TimeSeries class which is a container for historical data. It can be used for simple date/quote time series (eg here). Nice thing is that is can also be overloaded with QuantLib::IntervalPrice to create OHLC objects.

For some reason, QuantLib::IntervalPrice returns OCHL objects (?). It is not so much of a problem, yet it can cause confusion while calling methods/using inspectors. We then start by making changes to the QL‘ s prices.hpp file, essentially swapping constructor parameters/variable names. Notice all changes made are not required to get what we want to achieve; some are purely cosmetics.

Below is the diff prices.hpp file:

/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2015, Edouard 'tagoma' Tallent
 Copyright (C) 2006, 2007 Ferdinando Ametrano
 Copyright (C) 2006 Katiuscia Manzoni
 Copyright (C) 2006 Joseph Wang

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file prices.hpp
    \brief price classes
*/

#ifndef quantlib_prices_hpp
#define quantlib_prices_hpp

#include <ql/timeseries.hpp>
#include <ql/utilities/null.hpp>

namespace QuantLib {

    //! Price types
    enum PriceType {
         Bid,          /*!< Bid price. */
         Ask,          /*!< Ask price. */
         Last,         /*!< Last price. */
         Close,        /*!< Close price. */
         Mid,          /*!< Mid price, calculated as the arithmetic
                            average of bid and ask prices. */
         MidEquivalent, /*!< Mid equivalent price, calculated as
                            a) the arithmetic average of bid and ask prices
                            when both are available; b) either the bid or the
                            ask price if any of them is available;
                            c) the last price; or d) the close price. */
         MidSafe       /*!< Safe Mid price, returns the mid price only if
                            both bid and ask are available. */
    };

    /*! return the MidEquivalent price, i.e. the mid if available,
        or a suitable substitute if the proper mid is not available
    */
    Real midEquivalent(const Real bid,
                       const Real ask,
                       const Real last,
                       const Real close);

    /*! return the MidSafe price, i.e. the mid only if
        both bid and ask prices are available
    */
    Real midSafe(const Real bid,
                 const Real ask);

    //! interval price
    class IntervalPrice {
      public:
        enum Type { Open, High, Low, Close };

        IntervalPrice();
        IntervalPrice(Real open, Real high, Real low, Real close);
        //! \name Inspectors
        //@{
        Real open() const { return open_; }
        Real high() const { return high_; }
        Real low() const { return low_; }
        Real close() const { return close_; }
        Real value(IntervalPrice::Type) const;
        //@}

        //! \name Modifiers
        //@{
        void setValue(Real value, IntervalPrice::Type);
        void setValues(Real open, Real high, Real low, Real close);
        //@}

        //! \name Helper functions
        //@{
        static TimeSeries makeSeries(
            const std::vector& d,
            const std::vector& open,
            const std::vector& high,
            const std::vector& low,
            const std::vector& close);

        static std::vector extractValues(
                                          const TimeSeries&,
                                          IntervalPrice::Type);
        static TimeSeries extractComponent(
                                          const TimeSeries&,
                                          enum IntervalPrice::Type);
        //@}
      private:
          Real open_, high_, low_, close_;
    };

    
    template <>
    class Null 
    {
      public:
        Null() {}
        operator IntervalPrice() const { return IntervalPrice(); }
    };

}

#endif

We can now turn to our wrapper:

/*
Copyright (C) 2015, Edouard 'tagoma' Tallent
OHLC timeseries factory based on QuantLib 
QuantCorner @ https://quantcorner.wordpress.com
*/

#include <fstream>
#include <string>
#include <iostream>
#include <vector>
#include <memory>
#include <ql\quantlib.hpp>
#include <boost\algorithm\string\split.hpp>
#include <boost\algorithm\string\classification.hpp>

// Wrapper creating a QuantLib OHLC time series from *.csv file
QuantLib::TimeSeries<QuantLib::IntervalPrice> QL_OHLC(char *filename){
    std::ifstream file(filename);
    std::string line;
    std::vector<QuantLib::Real> open, close, high, low;
    std::vector<QuantLib::Date> dates;
    std::vector<std::string> tokens;
    QuantLib::TimeSeries<QuantLib::IntervalPrice> ochl;

    while (std::getline(file, line))
    {
        std::stringstream stringStream(line);
        std::string content;
        int item = 0;
        while (std::getline(stringStream, content, ',')) {
            switch (item) {
            case 0:
                boost::algorithm::split(tokens, content, boost::algorithm::is_any_of("/"));
                dates.push_back(QuantLib::Date(QuantLib::Day(std::stoi(tokens.at(0))),
                    QuantLib::Month(std::stoi(tokens.at(1))),
                    QuantLib::Year(std::stoi(tokens.at(2)))));
                break;
            case 1: open.push_back(std::stod(content)); break;
            case 2: close.push_back(std::stod(content)); break;
            case 3: high.push_back(std::stod(content)); break;
            case 4: low.push_back(std::stod(content)); break;
            }
            item++;
        }
    }
    return QuantLib::IntervalPrice::makeSeries(dates, open, high, low, close);
}


auto main(int argc, char *argv []) -> int
{
    // Function call
    char* file = argv[1];
    QuantLib::TimeSeries<QuantLib::IntervalPrice> ohlc = QL_OHLC(file);

    // Start date of the time series
    std::cout << "Start date: " << ohlc.firstDate() << std::endl;

    // Is the time series empty?
    std::cout << "Time series empty (1:Yes; 0:No): "
        << ohlc.empty() << std::endl;

    // Return the number of dates/time series length
    std::cout << "Series length (number of time steps): "
        << ohlc.size() << std::endl;

    // Access the OHLC values
    std::vector<QuantLib::IntervalPrice> v;
    for (auto i : ohlc)
        v.push_back(i.second);
        std::cout << "\nOpen\tHigh\tLow\tClose" << std::endl;
    for (auto i : v)
        std::cout << i.open() << "\t" << i.high() << "\t" << i.low()
        << "\t" << i.close() << std::endl;
    std::cout << "\n";

    // Etc ....

    return 0;
}

/*
/////////////////////////
/ Demo datafile (*.csv) /
/////////////////////////
11/05/2015,51.18,51.99,51.06,51.21
12/05/2015,51.64,52.12,51.24,51.95
13/05/2015,50.61,51.80,50.47,51.64
14/05/2015,50.61,50.67,50.00,50.27
15/05/2015,49.55,50.91,49.54,50.91
18/05/2015,48.68,49.50,48.51,48.95
19/05/2015,48.42,49.04,48.01,48.68
20/05/2015,48.83,49.19,48.00,48.52
21/05/2015,48.54,48.96,47.69,48.45
22/05/2015,49.31,49.40,48.19,48.64
25/05/2015,49.34,50.22,49.03,49.63
26/05/2015,48.28,48.79,47.84,48.33
27/05/2015,47.97,48.86,47.31,47.66
28/05/2015,49.12,49.19,47.55,48.06
29/05/2015,51.21,51.37,50.57,51.16
01/06/2015,51.52,51.58,51.03,51.20
02/06/2015,52.30,52.39,51.52,51.52
*/

/*
//////////////////
/ Program output /
//////////////////
Start date : May 11th, 2015
Time series empty(1:Yes; 0:No) : 0
Series length(number of time steps) : 17

Open    High    Low     Close
51.18   51.99   51.06   51.21
51.64   52.12   51.24   51.95
50.61   51.8    50.47   51.64
50.61   50.67   50      50.27
49.55   50.91   49.54   50.91
48.68   49.5    48.51   48.95
48.42   49.04   48.01   48.68
48.83   49.19   48      48.52
48.54   48.96   47.69   48.45
49.31   49.4    48.19   48.64
49.34   50.22   49.03   49.63
48.28   48.79   47.84   48.33
47.97   48.86   47.31   47.66
49.12   49.19   47.55   48.06
51.21   51.37   50.57   51.16
51.52   51.58   51.03   51.2
52.3    52.39   51.52   51.52

Press any key to continue . . .
*/

Generating correlated random numbers with Python

(Below is kinda note-to-self.  This is the re-write of an old blog entry, based on Generating correlated random numbers by Thijs van den Berg)

Say I want to generate 3 series of random numbers correlated according to the following correlation matrix:

\begin{bmatrix} 1.0 & 0.6 & 0.3 \\ 0.6 & 1.0 & 0.5 \\ 0.3 & 0.5 & 1.0  \end{bmatrix}

from scipy.linalg import cholesky

# Correlation matrix
corr_mat= np.array([[1.0, 0.6, 0.3],
                    [0.6, 1.0, 0.5],
                    [0.3, 0.5, 1.0]])

# Compute the (upper) Cholesky decomposition matrix
upper_chol = cholesky(corr_mat)

# Generate 3 series of normally distributed (Gaussian) numbers
rnd = np.random.normal(0.0, 1.0, size=(10**7, 3))

# Finally, compute the inner product of upper_chol and rnd
ans = rnd @ upper_chol

Now, one can check results are consistent:

# One can check results are consistent
from scipy.stats import pearsonr

corr_0_1 , _ = pearsonr(ans[:,0], ans[:,1])
#corr_0_1
#0.60013025775533102

corr_0_2 , _ = pearsonr(ans[:,0], ans[:,2])
#corr_0_2
#0.30049740204791148

corr_1_2 , _ = pearsonr(ans[:,1], ans[:,2])
#corr_1_2
#0.50026641543258898