CHAPTER 4

image

Common Libraries for Financial Applications

Financial code implemented in C++ uses programming libraries designed to simplify the creation of fast, standard-conformant classes. The best example of such libraries is the STL (standard template library) itself, a convenient library that is included with standard-compliant C++ compilers. The STL offers a set of generic, commonly used containers that may be applied to almost any situation. Knowing how to employ well the STL is one of the main skills necessary for effective C++ programming, especially in the context of high-performance software development—a common requirement for financial applications. In this chapter, you will learn programming recipes that clarify some of the most common uses of the STL for financial programming, including containers and algorithms.

The boost project provides another set of commonly used classes. Although the standard language committee does not officially support boost libraries, some of them have been used as the basis for additions to the last few versions of the C++ standard library. Therefore, a good understanding of the classes and templates included in the boost repository is valuable as a way to have early access to functionality that will only later be made available in all C++ implementations.

In the next few sections, we explore C++ recipes that illustrate how these classes and templates are used in financial applications. Examples of important library components explored here include

  • Vectors: these are containers used to manipulate objects of the same type.
  • Maps: STL containers that can be used to associate values to a set of keys, which can be of any type.
  • Algorithms: the STL also provides a rich set of algorithms that can be used to manipulate the standard containers. You can also extend the existing algorithms so that they can be applied to your own data structures. Similarly, you can use the ideas provided by STL to implement your own algorithms.
  • Boost libraries: the STL is the foundation for other useful libraries. Boost libraries are written by some of the C++ experts working on the language committee. Many of the components previously included in the boost library, such as shared_ptr, have since become part of the language.
  • Time and date handling: financial applications are usually related to the processing of prices over specific time periods. To make this possible, it is necessary to use libraries to handle date uniformly.

In the next sections, you will see a few selected programming recipes that explore some of these C++ libraries in the context of financial applications.

Handling Analyst Recommendations

One of the common events around a particular stock is the release of analyst recommendations. Create a C++ class that handles analyst recommendations and returns the average target price for the stock.

Solution

Analyst recommendations are an important part of the Wall Street ecosystem. Many financial institutions such as pension funds and insurance companies, as well as retail investors, use analysts’ recommendations as a gauge of the predominant view about a particular stock. This in turn can be used to determine future capital allocation to a portfolio of stocks.

Analyst recommendations come from one of the several institutions that provide public analysis of equity investments, generally from some of the major investment banks. The recommendation for a particular stock includes a defined action such as “buy,” “sell,” or “hold.” The recommendation also frequently includes a price target, which determines how much the analyst expects to be the “fair price” for the instrument.

Since there are so many analysts covering the equities market, keeping track of recommendations is one of the important parts of the work of an institutional investor. In this recipe, you will create a C++ class to store this type of information and to answer some basic questions such as “what is the average target price for a particular stock?”

The solution for this problem involves the use of STL containers to hold the data. In particular, you will use vectors to provide quick access to the data.

More About STL Vectors and Maps

The STL is a repository of standard data structures and algorithms that are useful in most programming domains. For financial applications in particular,  the use of the STL is extremely important because STL components are optimized for high speed. For example, STL components such as vectors are currently the preferred way to write applications in C++, instead of using raw C++ arrays and similar data-container classes. Compiler vendors have done a great job of making STL components fast and safe to use in a wide range of applications, so that programmers don’t need to worry about intricate issues such as memory allocation, exception handling, and algorithm complexity.

STL vectors are versatile because they can grow dynamically. Therefore, they work in situations where you don’t have a clear idea of how many elements will be stored in the underlying data structure (and as long as you don’t care about the small overhead of vector resizing). For example, if you’re reading trading data over a given time period, you typically don’t know how many trades occurred during that particular time frame. In this situation, it is easier to use a vector that can be initialized with a small number of elements and then grow as needed, instead of using an array with a predefined (and fixed) size. By using the STL vector in this way, you don’t need to worry about memory allocation and exception safety of the container.

The vector template exposes an interface with operations that can be applied to a set of elements, such as adding, removing, comparing, and sorting. These common operations can be used across concrete implementations, without any manual changes required. For example, vectors declared as std::vector<int> can use the push_back member function to add int elements to the end of the vector. Similarly, a second vector declared as std::vector<std::string> can also add std::string elements using the same template-based member function.

Following is a quick list of the most regularly used member functions in the std::vector template:

  • template <class T> vector(const T &c, int n): constructor used to create a new vector initialized with n copies of the constant element c.
  • template <class T> vector(const vector<T> &v): constructor used to create a new vector initialized with a copy of an existing vector object v.
  • template <class T> T &operator [] (int pos): this operator makes the std::vector object behave as a native array. You can use the notation v[i] to access or update the value of an element stored at position i of vector v.
  • template <class T> void push_back(const T &c): used to add a new element to the back of a vector, allocating additional memory if necessary. This is the most commonly used way to add new elements to a vector, since it takes care of adding memory to the store the new element when necessary, unlike the operator [], which will crash the application when an undefined position is accessed.
  • template <class T> void pop_back(): this member function performs the inverse of the push_back operation, removing the element stored in the last position of the vector. However, the memory allocated for that element is not immediately reclaimed and it may be used by later operations.
  • template <class T> size_t size(): this member function returns the number of elements currently stored in the std::vector. Notice that this may be less than the total memory currently used by the vector, since it is possible for the vector to allocate more memory than it currently needs, depending on the number of elements previously added or reserved.

In our problem we use vectors to store recommendations for a particular stock. Each stock covered by this class will have a vector of recommendations. Each recommendation is just an object of the Recommendation class, which stores recommendations defined in the following way:

enum RecommendationType {
    BUY,
    SELL,
    HOLD,
    NO_RECOMMENDATION
};

The Recommendation class is defined as

class Recommendation {
public:
    Recommendation();
    Recommendation(const std::string &ticker, RecommendationType rec, double target);
    ~Recommendation();
    Recommendation(const Recommendation &r);
    Recommendation &operator =(const Recommendation &r);

    double getTarget() const;
    RecommendationType getRecommendation() const;
    std::string getTicker() const;

    // private members
};

This simple Recommendation class stores the ticker for stock, as well as its recommendation type and price target. Notice that objects that are stored in a std::vector need to be from classes that define the copy constructor operator, since elements in a std::vector are stored by value. This means that a copy is created whenever there is the need to move the object to a certain position.

Using std::vector we can keep track of the individual recommendation for a stock. However, there are several stocks in the universe of equities that we would like to track. To find the right recommendations, we assign a way to retrieve objects based on the stock ticker. This is performed using a std::map template. Using a map will also simplify the code necessary to implement other useful operations, such as adding new recommendations or calculating the average recommendation.

The std::map template provides a way to associate an arbitrary key to a data object, so that you can easily retrieve the original data. The best thing about maps is that the key can be of any kind of object offering comparison operations. In our case, for example, you can use a string that indicates the unique ticker for each particular stock. Based on the ticker, you can retrieve the vector that contains the recommendations for that particular stock.

Here is a short list of important operations defined for std::map:

  • template <class K, class T> iterator<T> find(const K&): returns an iterator to the data object that is associated to the given key. If the key has no association, the function returns the end() iterator.
  • template <class K, class T> T &operator[](const K&): associates a key with a particular data object. You can use this operator to retrieve elements from the map container as well as inserting new elements.
  • template <class K, class T> size_t erase(const K&): erases the data associated with the given key.

The std::map template is used in this code example to store and retrieve recommendations that were issued for a particular stock. The RecommendationProcessor is the class responsible for storing, processing, and answering queries related to stock recommendations. The general algorithm used by RecommendationProcessor consists of storing new recommendations in an internal data structure. Then, at any future moment you can query the stored data using the averageTargetPrice member function.

The first member function in this class, addRecommendation, is responsible for storing new recommendations to the m_recommendations member variable.

void RecommendationsProcessor::addRecommendation(const std::string &ticker,
                                                 RecommendationType rec, double targetPrice)
{
    Recommendation r(ticker, rec, targetPrice);
    m_recommendations[ticker].push_back(r);
}

Here, you first need to create a new recommendation object based on the information passed, such as ticker, type of recommendation, and target price. Then, we use the m_recommendations map to access the vector of recommendations. Finally, we use the push_back method to add the new recommendation to the list of recommendations for that particular stock.

Another interesting member function is the one that calculates the average target price. It looks at all recommendations to calculate an average target.

double RecommendationsProcessor::averageTargetPrice(const std::string &ticker)
{
    if (m_recommendations.find(ticker) == m_recommendations.end())
        return 0;
    auto vrec = m_recommendations[ticker];
    std::vector<double> prices;
    for (auto i=0; i<vrec.size(); ++i)
    {
        prices.push_back(vrec[i].getTarget());
    }
    return std::accumulate(prices.begin(), prices.end(), 0) / prices.size();
}

The first thing you need to do in this code is to check if the stock has any recommendation. If not, the function returns the value zero. Otherwise, you can retrieve the recommendations using the [] operator. I added all the target prices to a temporary vector of prices, and used the std::accumulate algorithm to compute the sum of all prices. Finally, the member function returns the total divided by the number of such price recommendations, which is just the average target price as desired.

Complete Code

Listing 4-1 presents the complete code for the class Recommendation, as described in the previous section. The listing shows the header and implementation files that you will need to include the class in your project.

Listing 4-1. Definitions and Implementation for Class Recommendation

//
//  Recommendation.h

#ifndef __FinancialSamples__Recommendation__
#define __FinancialSamples__Recommendation__

#include <string>

enum RecommendationType {
    BUY,
    SELL,
    HOLD,
    NO_RECOMMENDATION
};

class Recommendation {
public:
    Recommendation();
    Recommendation(const std::string &ticker, RecommendationType rec, double target);
    ~Recommendation();
    Recommendation(const Recommendation &r);
    Recommendation &operator =(const Recommendation &r);

    double getTarget() const;
    RecommendationType getRecommendation() const;
    std::string getTicker() const;

private:
    std::string m_ticker;
    RecommendationType m_recType;
    double m_target;
};

#endif /* defined(__FinancialSamples__Recommendation__) */

//
//  Recommendation.cpp

#include "Recommendation.h"

Recommendation::Recommendation()
: m_recType(HOLD),
  m_target(0)
{

}

Recommendation::Recommendation(const std::string &ticker, RecommendationType rec, double target)
: m_ticker(ticker),
  m_recType(rec),
  m_target(target)
{

}

Recommendation::~Recommendation()
{

}

Recommendation::Recommendation(const Recommendation &r)
: m_ticker(r.m_ticker),
  m_recType(r.m_recType),
  m_target(r.m_target)
{

}

Recommendation &Recommendation::operator =(const Recommendation &r)
{
    if (this != &r)
    {
        m_ticker = r.m_ticker;
        m_recType = r.m_recType;
        m_target = r.m_target;
    }
    return *this;
}

double Recommendation::getTarget() const
{
    return m_target;
}

RecommendationType Recommendation::getRecommendation() const
{
    return m_recType;
}

std::string Recommendation::getTicker() const
{
    return m_ticker;
}

//
//  RecommendationsProcessor.h

#ifndef __FinancialSamples__RecommendationsProcessor__
#define __FinancialSamples__RecommendationsProcessor__

#include <map>
#include <vector>

#include "Recommendation.h"

class RecommendationsProcessor {
public:
    RecommendationsProcessor();
    ~RecommendationsProcessor();
    RecommendationsProcessor(const RecommendationsProcessor &);
    RecommendationsProcessor &operator =(const RecommendationsProcessor &);

    void addRecommendation(const std::string &ticker, RecommendationType rec, double     targetPrice);
    double averageTargetPrice(const std::string &ticker);
    RecommendationType averageRecommendation(const std::string &ticker);
private:
    std::map<std::string, std::vector<Recommendation> > m_recommendations;
};

#endif /* defined(__FinancialSamples__RecommendationsProcessor__) */

//
//  RecommendationsProcessor.cpp

#include "RecommendationsProcessor.h"
#include <numeric>

RecommendationsProcessor::RecommendationsProcessor()
{

}

RecommendationsProcessor::~RecommendationsProcessor()
{

}

RecommendationsProcessor::RecommendationsProcessor(const RecommendationsProcessor &r)
: m_recommendations(r.m_recommendations)
{

}

RecommendationsProcessor &RecommendationsProcessor::operator =(const RecommendationsProcessor &r)
{
    if (this != &r)
    {
        m_recommendations = r.m_recommendations;
    }
    return *this;
}

void RecommendationsProcessor::addRecommendation(const std::string &ticker,
                                                 RecommendationType rec, double targetPrice)
{
    Recommendation r(ticker, rec, targetPrice);
    m_recommendations[ticker].push_back(r);
}

double RecommendationsProcessor::averageTargetPrice(const std::string &ticker)
{
    if (m_recommendations.find(ticker) == m_recommendations.end())
        return 0;
    auto vrec = m_recommendations[ticker];
    std::vector<double> prices;
    for (auto i=0; i<vrec.size(); ++i)
    {
        prices.push_back(vrec[i].getTarget());
    }
    return std::accumulate(prices.begin(), prices.end(), 0) / prices.size();
}

RecommendationType RecommendationsProcessor::averageRecommendation(const std::string &ticker)
{
    double avg = 0;
    if (m_recommendations.find(ticker) == m_recommendations.end())
    {
        return NO_RECOMMENDATION;
    }
    auto vrec = m_recommendations[ticker];
    std::vector<int> recommendations;
    for (auto i=0; i<vrec.size(); ++i)
    {
        recommendations.push_back((int)vrec[i].getRecommendation()+1);
    }

    return (RecommendationType) (int) (avg / recommendations.size());
}

Performing Time-Series Transformations

Create a class that can be used to perform common time-series transformations, such as adding or subtracting values to prices and removing undesired values.

Solution

Time-series data filtering is the task of identifying and removing values, both for short- and long-term trends, in a sequence of data points. From the point of view of programming, this is performed by the application of a series of transformations to data stored in containers. This is a very common task that is properly covered by the STL. The STL provides several templates that simplify the execution of common algorithms such as sorting and selection, which can be applied to data containers such as vectors and lists. Such algorithms can be accessed in C++ code by including the header file <algorithm>. No additional work is necessary on the part of the programmer.

The <algorithm> header file provides declarations for many useful functions. Among them, you will find

  • copy: this template function is used to copy a range of elements from a given container into a second container. Notice that, as with other generic algorithms, the containers don’t need to be of the same type. For example, a range of a vector can be copied into a map, and vice versa.
  • copy_backward: similar to the copy function, but the process is performed from the last to first element of given range. This can be used, for example, to write the elements of a container in the reverse order.
  • for_each: this algorithm can be used to apply a particular function or function object to a set of elements in a container. The for_each algorithm can be used to avoid the use of a for loop over the elements of a container. If the operation defined by the loop can be quickly encapsulated within a function or function object, the for_each algorithm can be a more concise way to perform the same operation.
  • find_if: used to find elements in a given range of a generic container. The last parameter for the find algorithm can be a function or a function object that is used to determine the property satisfied by the desired element.
  • count: return the number of elements in a given range of a generic container.
  • count_if: return the number of elements in a given range of generic containers that satisfy the function or function object passed as the third parameter.
  • transform: this generic function takes a range of elements in a container, a destination container, and a transformation function object. The elements of the input container are transformed using the transformation function and stored in the destination container.
  • fill: this function is used to fill a given range of a container with a single element.
  • reverse: changes the given range of a generic container so that the order of the elements in the container is reversed.
  • sort: a generic algorithm that can be used to sort a sequence of elements stored in an STL container. The first two parameters for this algorithm define the range of elements. The third parameter is a comparison function, which is used to determine if two elements are correctly ordered.
  • binary_search: implements a binary search of elements over a sorted range in a container.
  • min_element: returns the element with minimum value among the elements stored in the given container.
  • max_element: returns the element with maximum value among the elements stored in the given container.

These are the most common algorithms provided by the STL. While these algorithms are in most cases simple to code, there are some advantages to using the STL algorithm templates over manually created implementations:

  • The first advantage is that they reduce the possibility of errors when implementing similar operations. For example, the for_each algorithm just applies the same function to all elements in a range. While this is easy to do with a for loop, there is always the possibility of making mistakes when manipulating individual elements of the container. The for_each algorithm, however, has all the logic contained in the template definition, reducing the possibility of errors.
  • Algorithms in the STL have intimate knowledge of how containers work. Implementers of the STL understand subtle nuances of the containers, which can greatly improve the performance of these algorithms. By using partial specialization, the authors of the STL can tailor such algorithms to achieve maximum performance for each container. Using the STL, you automatically take advantage of this knowledge in your application.
  • Algorithms are also a succinct way to describe your intent. Instead of write another for loop to find a minimum element, for example, you can apply the min_element algorithm to the target container.

Using STL Algorithms

To solve the problem posed in this section, you will create a class, TimeSeriesTransformations, which implements a few time-series transformation operations. The first algorithm implemented is used to reduce prices in the series. The solution relies on the std::transform algorithm, and it is implemented as follows:

void TimeSeriesTransformations::reducePrices(double val)
{
    std::vector<double> res;
    std::transform(m_prices.begin(), m_prices.end(), res.begin(),
       std::bind2nd(std::minus<double>(), val));
    m_prices.swap(res);
}

In this member function, the first step is to apply std::transform to the vector m_prices. The first two parameters are the iterators for the beginning and the end of the vector. Then, you need to pass the beginning of the output vector. Finally, the last parameter is the function object used to perform the transformation. The template std::bind2nd is declared in the <functional> header file, and allows one to bind the second parameter of a functional object (in this case, the minus function). The result is that the minus function will be applied to the m_prices vector, with the second parameter set to the value defined by val. After the transformation is performed, the next step is to swap the values stored in m_prices with the values stored in the result vector.

The second function is similar, but I used an alternative strategy.

void TimeSeriesTransformations::increasePrices(double val)
{
    std::for_each(m_prices.begin(), m_prices.end(), std::bind1st(std::plus<double>(), val));
}

Here, the function template std::for_each is used to perform a transformation to each element of the original vector. In this way, you can avoid the need to swap values into and out of the container. The for_each function applies the plus function with the first parameter bound to the passed value. As a result, all prices are increased as desired.

The TimeSeriesTransformations class also includes a few other methods that explore the STL algorithms. For example, the removePricesLessThan method uses the remove_if template to eliminate prices that are less than the given value. The member function removePricesGreaterThan is similar. Finally, the getFirstPriceLessThan function uses the find_if template function to identify a price that is less than a given value, if such a price exists.

Complete Code

The algorithm described previously has been implemented using a C++ class called TimeSeriesTransformations. It is divided into a header file TimeSeriesTransformations.h and a source file TimeSeriesTransformations.cpp. Listing 4-2 contains the complete code.

Listing 4-2. Class TimeSeriesTransformations

//
//  TimeSeriesTransformations.h

#ifndef __FinancialSamples__TimeSeriesAnalysis__
#define __FinancialSamples__TimeSeriesAnalysis__

#include <vector>

class TimeSeriesTransformations {
public:
    TimeSeriesTransformations();
    TimeSeriesTransformations(const TimeSeriesTransformations &);
    ~TimeSeriesTransformations();
    TimeSeriesTransformations &operator=(const TimeSeriesTransformations &);
    void reducePrices(double val);
    void increasePrices(double val);
    void removePricesLessThan(double val);
    void removePricesGreaterThan(double val);
    double getFirstPriceLessThan(double val);
    void addValue(double val);
    void addValues(const std::vector<double> &val);
private:
    std::vector<double> m_prices;
};

#endif /* defined(__FinancialSamples__TimeSeriesAnalysis__) */

//
//  TimeSeriesTransformations.cpp

#include "TimeSeriesTransformations.h"

#include <algorithm>
#include <functional>

TimeSeriesTransformations::TimeSeriesTransformations()
: m_prices()
{
}

TimeSeriesTransformations::TimeSeriesTransformations(const TimeSeriesTransformations &s)
: m_prices(s.m_prices)
{
}

TimeSeriesTransformations::~TimeSeriesTransformations()
{
}

TimeSeriesTransformations &TimeSeriesTransformations::operator=(const TimeSeriesTransformations &v)
{
    if (this != &v)
    {
        m_prices = v.m_prices;
    }
    return *this;
}

void TimeSeriesTransformations::reducePrices(double val)
{
    std::vector<double> neg(m_prices.size());
    std::transform(m_prices.begin(), m_prices.end(), neg.begin(),
       std::bind2nd(std::minus<double>(), val));
    m_prices.swap(neg);
}

void TimeSeriesTransformations::increasePrices(double val)
{
    std::for_each(m_prices.begin(), m_prices.end(), std::bind1st(std::plus<double>(), val));
}

void TimeSeriesTransformations::removePricesLessThan(double val)
{
    std::remove_if(m_prices.begin(), m_prices.end(), std::bind2nd(std::less<double>(), val));
}

void TimeSeriesTransformations::removePricesGreaterThan(double val)
{
    std::remove_if(m_prices.begin(), m_prices.end(), std::bind2nd(std::greater<double>(), val));
}

double TimeSeriesTransformations::getFirstPriceLessThan(double val)
{
    auto res = std::find_if(m_prices.begin(), m_prices.end(),
         std::bind2nd(std::less<double>(), val));
    if (res != m_prices.end())
        return *res;
    return 0;
}

void TimeSeriesTransformations::addValue(double val)
{
    m_prices.push_back(val);
}

void TimeSeriesTransformations::addValues(const std::vector<double> &val)
{
    m_prices.insert(m_prices.end(), val.begin(), val.end());
}

int main()
{
    TimeSeriesTransformations ts;
    std::vector<double> vals = {7, 6.4, 2.16, 5, 3, 7};
    ts.addValues(vals);
    ts.addValue(6.5);
    ts.reducePrices(0.5);
    std::cout << " price is " <<  ts.getFirstPriceLessThan(6.0) << std::endl;
    return 0;
}

Running the Code

I included some sample code that uses the TimeSeriesTransformations class in the main() function. In this way, you can compile the files presented in the previous section into a sample application. After building the application with the help of a C++ compiler such as gcc or Visual Studio, you may run it using the following command line:

./TimeSeriesTransformations
 price is 5.9

Copying Transaction Files

Create a class to copy transaction files into a temporary storage.

Solution

File operations are a common requirement in a lot of areas of programming and it couldn’t be different in financial applications. Data is commonly stored in formats such as CSV and XML, as they need to be processed and filtered by other applications. Logging facilities are also necessary to guarantee that debugging and error messages are properly handled.

This type of file manipulation problem can be solved using traditional C interfaces, which are available for all major operating systems such as UNIX and Windows. However, such native interfaces have a few shortcomings and should be avoided when possible. I use this opportunity to provide an overview of a better approach, using the boost repository of C++ libraries, and the filesystem library in particular. With a basic knowledge of how boost works, you will be in a position to use other, more complex classes in the next few chapters.

Boost Libraries

The standard C++ library provides a large number of classes, containers, and algorithms. However, due to the substantial effort necessary to create a new standard of the C++ language, the included set of libraries is frequently minimalist, comprising only the essential functionality needed by most programmers. Moreover, the standard library incorporates only classes and functions that have been well verified and established by their use in real applications, having stood the test of time.

Due to the slow process of including new functionality in the language standard, a more agile strategy for software distribution and development was needed, as a way to better incorporate new libraries approved by the C++ community. Boost libraries were created to fill the gap left by this slow process of including new functionality in the standard. Unlike traditional single-vendor libraries, the goal for boost developers is to create high-level components that can be reusable across a large spectrum of domains and architectures. Many of the contributors to boost are themselves involved in the development of the C++ standard, which means that many of the libraries currently included in the boost distribution will later on become part of the standard library. For example, template classes such as std::shared_ptr originated from boost::shared_ptr.

To use boost libraries in your application, you need to download and install them from the boost.org web site. The process is made simpler because of the nature of boost classes. Since most components in boost are defined as template classes, the complete code is, with a few exceptions, contained in the header files. This means that you can use all the functionality in certain boost libraries by simply adding a header file to your code.

Table 4-1 shows some of the libraries available in the boost distribution.

Table 4-1. Some of the Most Commonly Used Components in the Boost Distribution

Library

Description

boost:any

A polymorphic data type designed to be used as a container to any other data type

Bind

Allows existing functions to be used by other function objects

Circular Buffer

Defines a storage template that can be used as a circular buffer

Chrono

A set of time-related utilities

Filesystem

An implementation of common file operations, including coping, moving, and creating files or directories

Foreach

Introduces a looping construct (deprecated by new features on C++11)

Function

A set of templates that define function wrappers

Geometry

An implementation of common geometric algorithms

Graph

Defines a graph data type, as well as common graph theory techniques and algorithms

Hash

A simple hash table data type, defined as a template

Lambda

A set of templates that can be used to write lambda functions for functional programming

Log

A generic logging facility for C++ applications

Math

Additional math functions

MPI

A message passing interface library

PropertyMap

Defines a generic property template, which can be used to define dynamic attributes

SmartPtr

A set of smart pointer types for storage of heap-allocated objects

In this chapter and the next, we will have the opportunity to explore some of these libraries, as they will be needed in other financial C++ recipes. In this recipe, we are concerned with the filesystem library, used to provide file and directory-related operations.

C and C++ have traditionally provided interfaces for file handling in each of the platforms where it has been implemented. Platform vendors have created separate libraries for this purpose, resulting in different interfaces for operating systems such as UNIX and other Posix-compliant systems, Windows, OS/2, VMS, and others. The differences between these interfaces, however, make it difficult to port applications across systems. Application writers have, in practice, created abstraction layers that interact with each different system as needed.

The filesystem library in boost is an attempt to provide a set of cross-platform classes and templates for file manipulation. The same classes and templates can be used to handle files in each of the platforms that support boost. This reduces the amount of work by application programmers, while the resulting code can be reused in other platforms without risks.

The main components of the library are included in the boost::filesystem namespace. These components allow you to perform common operations on files and directories. The classes included in the library easily support operations such as copying, moving, changing permissions, and removing. Next, I show you some of these important components and how they can be used to write code to manipulate file system contents.

The boost::filesystem::path class represents a path in the file system. Having a path class is interesting because it lets programmers represent directory paths in different systems, while using the same object. For example, paths in a UNIX system use the “/” separator, while in Windows the “” separator is used. To avoid problems associated with these different conventions, the filesystem library uses a common representation.  The class path is then used by many of the functions provided in the library.

The other important concept of the filesystem library is that of directory iterators. You can create an iterator for a particular path using the directory_iterator function. This function returns an iterator object, which has operators such as ++ and --, allowing programmers to move between elements of the given directory.

Other than suitable abstractions for paths and iterators, the filesystem library provides a set of functions that can be used to perform individual changes to files and directories. These functions include the following:

  • is_regular_file: returns true if the path supplied indicates a regular file (instead of a directory).
  • is_directory: returns true if the path indicates a directory.
  • file_size: returns the size of the file name passed as argument, in bytes.
  • exists: returns true if the path passed as argument exists in the file system.
  • status: returns a file_status object, which encapsulates the properties of given file, such visibility and type.
  • create_directory: creates a new directory in the file system, with the given path.
  • copy: makes a copy of the given path to a location indicated by the second parameter.
  • remove: removes the given path from the file system.
  • current_path: returns a path object that indicates the current path used by the application.

These functions can be easily combined to manipulate the file system. I used some of these functions to implement all the functionality needed by the FileManager class. For example, following is how the getContents member function is coded:

std::vector<std::string> FileManager::getContents(const std::string &prefix)
{
    std::vector<std::string> results;
    path aPath(prefix);
    if (!is_directory(aPath))
    {
        std::cout << " incorrect path was used " << std::endl;
    }
    else
    {
        std::vector <path> contents;
        copy(directory_iterator(aPath), directory_iterator(), back_inserter(contents));

        for (int i=0; i<contents.size(); ++i)
        {
            results.push_back(contents[i].string());
        }
    }
    return results;
}

The first step is to create a path object based on the string passed as a parameter. Once the path has been created, you can test if it points to a directory using the is_directory function. If the path is correct, then you can then use the directory_iterator function to create an iterator object, which is passed to the copy function. The copy function’s only job is to copy each element pointed by the iterator into the contents vector. The elements in this container are later converted to strings and added to the results vector.

Finally, the function that copies files from one directory to a given destination can be added to the FileManager class using the following code:

void FileManager::copyToTempDirectory(const std::string &prefix)
{
    path tmpPath("/tmp/");
    path aPath(prefix);
    if (!is_directory(aPath))
    {
        std::cout << " incorrect path was used " << std::endl;
        return;
    }
    std::cout << " copying the following files: " << std::endl;
    this->listContents(prefix);

    for (auto it = directory_iterator(aPath); it != directory_iterator(); ++it)
    {
        if (is_regular_file(it->path()))
        {
            copy_file(it->path(), tmpPath);
        }
    }
}

Here, you start checking the given path prefix to make sure that it is a reference to a directory. Then, I have added some code to list the contents as a form of logging. The next step is to iterate through the content of the directory using the iterator returned by the directory_iterator function. For each element of the directory, you can test if it is a regular file, and then use the copy_file function to perform the copy.

Complete Code

You can see the complete definition of the FileManager class in Listing 4-3. At the end of the listing you can see an example of how to use the class in the main() function.

Listing 4-3. Definitions and Implementation for Class FileManager

//
//  FileManager.h

#ifndef __FinancialSamples__FileManager__
#define __FinancialSamples__FileManager__

#include <string>
#include <vector>

class FileManager {
public:
    FileManager(const std::string &basePath);
    FileManager(const FileManager &);
    ~FileManager();
    FileManager &operator=(const FileManager &);

    void removeFiles();
    std::vector<std::string> getDirectoryContents();
    void listContents();
    void copyToTempDirectory(const std::string &prefix);

private:
    std::string m_basePath;
};

#endif /* defined(__FinancialSamples__FileManager__) */

//
//  FileManager.cpp

#include "FileManager.h"

#include <boost/filesystem/operations.hpp>

using namespace boost::filesystem;

FileManager::FileManager(const std::string &basePath)
: m_basePath(basePath)
{

}

FileManager::FileManager(const FileManager &v)
: m_basePath(v.m_basePath)
{

}

FileManager::~FileManager()
{

}

FileManager &FileManager::operator=(const FileManager &v)
{
    if (this != &v)
    {
        m_basePath = v.m_basePath;
    }
    return *this;
}

void FileManager::removeFiles()
{
    std::vector<std::string> files = getDirectoryContents();
    for (unsigned i=0; i<files.size(); ++i)
    {
        path aPath(files[i]);

        if (is_regular_file(aPath))
        {
            std::cout << " path " << files[i] << " is not a regular file " << std::endl;
        }
        else
        {
            remove(aPath);
        }
    }
}

void FileManager::listContents()
{
    std::vector<std::string> files = getDirectoryContents();
    for (unsigned i=0; i<files.size(); ++i)
    {
        path aPath(files[i]);
        if (is_regular_file(aPath))
        {
            std::cout << aPath.string() << std::endl;
        }
    }
}

std::vector<std::string> FileManager::getDirectoryContents()
{
    std::vector<std::string> results;
    path aPath(m_basePath);
    if (!is_directory(aPath))
    {
        std::cout << " incorrect path was used " << std::endl;
    }
    else
    {
        auto iterator = directory_iterator(aPath);

        std::vector <path> contents;
        copy(directory_iterator(aPath), directory_iterator(), back_inserter(contents));

        for (unsigned i=0; i<contents.size(); ++i)
        {
            results.push_back(contents[i].string());
        }
    }
    return results;
}
void FileManager::copyToTempDirectory(const std::string &tmpDir)
{
    const path tmpPath(tmpDir);
    path aPath(m_basePath);
    if (!is_directory(aPath))
    {
        std::cout << " incorrect path was used " << std::endl;
        return;
    }
    std::cout << " copying the following files: " << std::endl;
    this->listContents();

    std::vector<std::string> contents = getDirectoryContents();
    for (auto it = directory_iterator(aPath); it != directory_iterator(); ++it)
    {
        if (is_regular_file(it->path()))
        {
            copy_file(it->path(), tmpPath);
        }
    }
}
int main()
{
    // create a FileManager object for the /tmp directory
    //
    FileManager fm("/tmp/");
    std::vector<std::string> contents = fm.getDirectoryContents();
    std::cout << "entries: " << std::endl;
    for (std::string entry : contents)
    {
        std::cout << entry << std::endl;
    }
    return 0;
}

Running the Code

The class FileManager presented in Listing 4-3 can be built using any standard C++ compiler, such as gcc or Visual Studio. The only extra library you will need is the boost::filesystem library. Many modern C++ distributions already include code from the boost project, but if needed you can manually download and install this library from the boost project web site.

For example, the command line necessary to build this class using gcc in my system is

gcc -o FileManager FileManager.cpp -I/usr/include/boost  -lboost_filesystem-mt

Once the application is generated, you can run it on a UNIX system as

./FileManager

This will display a list of files stored in the /tmp directory (you can change that directory as necessary to test on a path in your own system).

Handling Dates

Create a class that can be used to determine trading days for common securities, which are negotiated from Monday to Friday.

Solution

Dates are such a common part of financial data that you should have a well-defined way to deal with them. Dates are an integral part of historical prices, as well as important events for equity analysis, such as earnings releases, dividends, price splits, and other regulatory actions. The same can be said about fixed income, derivatives, and other investment classes. C++ provides a wealth of features that can be used to store, calculate, and transform dates from one format to another.

Although there are many time- and date-related functions and classes in C++, many of these mechanisms have been inherited from C standard libraries and are not as easy to use as other components of the STL. To smooth this process of integration with the STL, the boost repository includes a date_time library that specializes in handling different representations of dates, as well as providing the basic support for calculations based on different date formats.

To solve the problem posed by this recipe, you will use a class called Date, which encapsulates the concept of date as used by the application. The member variables are simply three values representing the year, month, and day. There is also the concept of days of the week, which are encoded in an enumeration.

enum DayOfWeek {
    Sun,
    Mon,
    Tue,
    Wed,
    Thu,
    Fri,
    Sat
};

The Date class exposes a number of member functions that can be used to answer common requests, such as getDayOfWeek, which returns the day of the week for the current date, and isLeapYear, which tells if a year has 29 days in February. Here is a quick list of member functions for Date.

Date(int year, int month, int day);
    ~Date();

    bool isLeapYear();
    Date &operator++();
    bool operator<(const Date &d);
    DayOfWeek getDayOfWeek();
    int daysInterval(const Date &);
    bool isTradingDay();

The isLeapYear method implementation uses the well-known definition of leap year, which considers years that are divisible by 4, 100, and 400:

bool Date::isLeapYear()
{
        if (m_year % 4 != 0) return false;
        if (m_year % 100 != 0) return true;
        if (m_year % 400 != 0) return false;
        return true;
}

The getDayOfWeek finds the day of the week for any date after January 1, 1900 (a Monday). It does so by counting the days since that date and updating the years, months, and days as necessary. The task of correctly adding to the current date is handled in operator +.

Finally, the Date class computes the difference between dates using the help of the date_time library from boost. In date_time, dates are classified according to a calendar. The calendar used in the western world is called the Gregorian calendar. It can be used after you include the following header file:

<boost/date_time/gregorian/gregorian.hpp>

The date_time library defines a few generic data types that can be used for date manipulation. In this recipe, we are interested in the date and date_duration types. The date type is just a representation of a date, and can be initialized with a year, month, and day. The date_duration is used to store the difference between dates. A duration type can be converted to an integer type using the days() member function. Here is how you can implement daysInterval

int Date::daysInterval(const Date &d)
{
    date bdate1(m_year, m_month, m_day);
    date bdate2(d.m_year, d.m_month, d.m_day);

    boost::gregorian::date_duration duration = bdate1 - bdate2;
    return (int) duration.days();
}

Complete Code

You can see the complete code for the class Date in Listing 4-4. Listing 4-4 includes a header file and an implementation file for the class. You will also see a main() function that creates two objects of type Date and performs some simple operations with them.

Listing 4-4. Implementation for Class Date

//
//  Date.h

#ifndef __FinancialSamples__Date__
#define __FinancialSamples__Date__

#include <string>

class Date {
public:

    enum DayOfWeek {
        Sun,
        Mon,
        Tue,
        Wed,
        Thu,
        Fri,
        Sat
    };

    Date(int year, int month, int day);
    ~Date();

    bool isLeapYear();
    Date &operator++();
    bool operator<(const Date &d);
    DayOfWeek getDayOfWeek();
    int daysInterval(const Date &);
    bool isTradingDay();
    std::string toStringDate(Date::DayOfWeek day);

private:
    int m_year;
    int m_month;
    int m_day;
};

#endif /* defined(__FinancialSamples__Date__) */

//
//  Date.cpp

#include "Date.h"

#include <vector>
#include <algorithm>

#include <boost/date_time/gregorian/gregorian.hpp>

using namespace boost::gregorian;

Date::Date(int year, int month, int day)
: m_year(year),
  m_month(month),
  m_day(day)
{
}

Date::~Date()
{
}

bool Date::isLeapYear()
{
        if (m_year % 4 != 0) return false;
        if (m_year % 100 != 0) return true;
        if (m_year % 400 != 0) return false;
        return true;
}

Date &Date::operator++()
{
        std::vector<int> monthsWith31 = { 1, 3, 5, 7, 8, 10, 12 };

        if (m_day == 31)
        {
                m_day = 1;
                m_month++;
        }
        else if (m_day == 30 &&
                 std::find(monthsWith31.begin(),
                           monthsWith31.end(), m_month) == monthsWith31.end())
        {
                m_day = 1;
                m_month++;
        }
        else if (m_day == 29 && m_month == 2)
        {
                m_day = 1;
                m_month++;
        }
        else if (m_day == 28 && m_month == 2  && !isLeapYear())
        {
                m_day = 1;
                m_month++;
        }
        else
        {
                m_day++;
        }

        if (m_month > 12)
        {
                m_month = 1;
                m_year++;
        }
        return *this;
}

int Date::daysInterval(const Date &d)
{
    date bdate1(m_year, m_month, m_day);
    date bdate2(d.m_year, d.m_month, d.m_day);

    boost::gregorian::date_duration duration = bdate1 - bdate2;
    return (int) duration.days();
}

bool Date::operator<(const Date &d)
{
    if (m_year < d.m_year) return true;
    if (m_year == d.m_year && m_month < d.m_month) return true;
    if (m_year == d.m_year && m_month == d.m_month && m_day < d.m_day) return true;
    return false;
}

Date::DayOfWeek Date::getDayOfWeek()
{
    int day = 1;
    Date d(1900, 1, 1);
    for (;d < *this; ++d)
    {
        if (day == 7) day = 1;
        else day++;
    }
    return (DayOfWeek) day;
}

bool Date::isTradingDay()
{
    DayOfWeek dayOfWeek = getDayOfWeek();
    if (dayOfWeek == Sun || dayOfWeek == Sat)
    {
        return false;
    }
    return true;
}

std::string Date::toStringDate(Date::DayOfWeek day)
{
    switch(day)
    {
        case DayOfWeek::Sun: return "Sunday";
        case DayOfWeek::Mon: return "Monday";
        case DayOfWeek::Tue: return "Tuesday";
        case DayOfWeek::Wed: return "Wednesday";
        case DayOfWeek::Thu: return "Thursday";
        case DayOfWeek::Fri: return "Friday";
        case DayOfWeek::Sat: return "Saturday";
    }
    throw std::runtime_error("unknown day of week");
}

int main()
{
    Date myDate(2015, 1, 3);
    auto dayOfWeek = myDate.getDayOfWeek();
    std::cout << " day of week is "
        << myDate.toStringDate(dayOfWeek) << std::endl;
    Date secondDate(2014, 12, 5);
    ++secondDate;  // test increment operator
    ++secondDate;

    int interval = myDate.daysInterval(secondDate);
    std::cout << " interval is " << interval << " days" << std::endl;
    return 0;
}

Running the Code

The code presented in Listing 4-4 uses only standard classes that are available to any standard C++ compiler. You can build an application using the following command line on Linux and other UNIX systems:

gcc -o Date Date.cpp

Executing the resulting binary will show the output of the test code include in the main() function:

./Date
 day of week is Saturday
 interval is 27 days

Conclusion

In this chapter, I presented programming recipes that cover basic libraries used in financial programming. These include the STL, with its set of containers (such as vector, map, and queue) and algorithms (such as sort, transform, and for_each, among others). You have also learned about the boost repository, a group of libraries that has been created to fill the gap resulting from the slow standardization process in C++.

Algorithms are an extensive part of the STL. These algorithms can be used to perform common operations such as search, copy, and transform, in any container defined by STL templates. You have also learned in this chapter how to apply these algorithms to data containers in order to perform data analysis.

The first recipe shows how to handle analyst recommendations. To properly process this type of information, you had to use STL vectors and maps. The second recipe uses algorithms provided by the STL to perform simple transformations in a time series. This kind of transformation can be used to clean up data, perform what-if analysis, and update prices according to the requirements of new techniques for investment analysis. You have seen how this can be done using STL algorithms and functional templates.

You have also learned, in the next recipe, how to create C++ code that handles files and directories in a way that is independent of the platform or operating system. This is accomplished using the filesystem library, which is part of the boost repository. One of the main advantages of using boost is that, while other libraries are closely tied to the operating system, boost libraries are written in a platform-independent way, following the same strategies employed by the standard library. In fact, many components of the boost have become part of the standard library over the years.

Another important aspect of financial code is the frequent use of dates. This type of data is associated with trades, analyst recommendations, dividends, and so many other events related to an investment. You learned how to use the date type in the boost date_time library, as well as how to compute other interesting properties of dates.

This concludes a set of coding recipes that reviews some basic aspects of modern C++ programming. You need to be aware of such techniques, which are mostly based on the use of templates, the STL, and their algorithms. It is also important to learn about extension libraries such as the boost repository. In the next chapter, you will start to learn more about the design of numerical classes. Financial applications in C++ make heavy use of numerical facilities to perform quick and accurate calculations of the desired properties of different investment classes. I will discuss some of the underlying principles in creating such numerical classes and how modern C++ libraries can help you to simplify the resulting code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.75.70