Series and Bounds Errors


Tutorial 111 illustrates how a simple moving average() function is used. While such a calculation is straight forward, there is a challenge in ensuring that the series object passed as argument has enough data for the function to complete the required calculation. The function signature for average() is:

 

double average(const series<double>& data, size_t period);

 

The first parameter is of type const series<double>&. Class series<T> is a container class that is similar to class std::vector<T> of the Standard Template Library (STL). The primary difference between the two classes is that for objects of type series<T>, the most recently added value is always found at index position 0, that is, at the 'front' of the series. The strategy's in_stream object, declares a number of such series<T> member objects with names such as 'open', 'high', 'low', 'close' (among others), each corresponding to a field/column of the same name in the underlying data source to which the in_stream object is connected.

 

In order to calculate a 10 bar average, the average() function requires access to the 10 most recent data items in the series. These are normally found at positions 0-9. However, when the strategy simulation first starts out, the various series<T> members of in_stream have no data at all. They are empty! When trying to access values in an insufficiently long series object, an exception of type tsa::series_bounds_error is thrown.

 

A series-bounds-error exception signals an 'insufficient data' condition. There is otherwise nothing malfunctioning or broken. It is thus safe for the strategy to intercept exceptions of this type and handle them silently. To enable the strategy to do this, the strategy::catch_series_bounds_errors(true) member must be invoked. Once this internal flag is set, the strategy will catch all exceptions of type series_bounds_error, and simply move the strategy to the subsequent bar. On the 10th bar, the series object finally has sufficient data and strategy evaluation subsequently runs clean. 

 

IMPORTANT: TS-API implements an alternate mechanism that avoids series-bounds-error exceptions. This mechanism is referred to as Domain Specific Language (DSL). This mechanism will be covered in detail chapter 3. DSL is the preferred method of working with time series. Almost all library series functions are implemented via DSL as the syntax is more intuitive for complex logic.  

 

Tutorial 111:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(1)

 

(2)

 

 

(3)

 

 

(4)

 

 

 

 

 

 

 

 

#include "tsa.h"

using namespace tsa;

 

     class my_strategy : public strategy

          {

                    in_stream sp500;

 

                    void on_start(void)   override

                    {

                              sp500.connect("sp500.daily");

                    }

 

                    void on_bar_close(void)    override

                    {

                catch_series_bounds_errors(true);

 

                     double sma5  = average(sp500.close, 5);

                                   double sma10 = average(sp500.close, 10);

 

                catch_series_bounds_errors(false);

 

                              std::cout

                                        << "date   :  " << (date)timestamp() << std::endl

                                        << "  close:  " << sp500.close << std::endl

                                        << "  sma5 :  " << sma5 << std::endl

                                        << "  sma10:  " << sma5 << std::endl;

                    }

          };

 

 

          fast::database db("output/demo_db", create_db | truncate_db);

 

          import_from_file__SP500_daily(db);

 

      my_strategy s;

          s.attach(db);

          s.run("2014-02-01", "2014-03-01");

 

Program Output

 

Tutorial:    111

==============================

date   :  2014-02-13               <- starts 10 'trading days' after strategy start

  close:  1824.3

  sma5 :  1808.64

  sma10:  1808.64

date   :  2014-02-14

  close:  1835

  sma5 :  1816.94

  sma10:  1816.94

date   :  2014-02-18

  close:  1837.4

  sma5 :  1825.48

  sma10:  1825.48

date   :  2014-02-19

  close:  1825.5

  sma5 :  1827.86

  sma10:  1827.86

(...lines skipped...)

press any key to exit...

 

 

 

(1)

Since we expect the strategy to throw series_bounds_error exceptions when it first starts the simulation, we need to explicitly instruct it to catch these exceptions and move on to the next bar. We do this with a call to:

 

 catch_series_bounds_errors(true);

(2)

The following two lines calculate two simple moving averages. Note that the function is simply called average() as all indicators are implicitly 'moving' since they are evaluated iteratively over consecutive bars.

 

double sma5  = average(sp500.close, 5);

double sma10 = average(sp500.close, 10);

 

Functions like this are easy to implement and are often sufficiently capable for a given task. However, they do have some shortcomings. They cannot be nested! The average() function returns a single value but declares an input parameter of type const series<double>&. So the value returned by average() cannot serve as input argument to a subsequent call of average(). So for more complex applications, the library's Domain Specific Language (DSL) extension needs to be used. All DSL functions return series, and can thus be nested.

 

Another shortcoming of such basic functions is their 'stateless' nature. This means that self referential algorithms, such as the 'exponential moving average' cannot be implemented. Here again using the DSL extension, as presented in the next chapter, solves the problem.

(3)

catch_series_bounds_errors(false);

 

The strategy's ability to silently handle series-bounds-errors should always be disabled again after the code that throws them is successfully evaluated. Disabling this essentially turns series_bounds_error exceptions into regular exceptions that would terminate the strategy immediately.

 

We do this because we want to ensure that series_bounds_error exceptions cannot disrupt the control of flow of code that deals with trading orders. This would unnecessarily complicate matters.

(4)

 The following line prints the current price as well as the current values for the calculated moving averages.

 

                    std::cout

                              << "date   :  " << (date)timestamp() << std::endl

                              << "  close:  " << sp500.close << std::endl

                              << "  sma5 :  " << sma5 << std::endl

                              << "  sma10:  " << sma5 << std::endl;

 

As expected, the first printout is delayed due to series_bounds_error exceptions that the strategy intercepts silently. As a result of these exceptions the code intended to print to std::cout is not evaluated at all for a number of bars.

 

Strategy start: "2014-02-01"

 

First line printed:  2014-02-13

 

This gap is slightly more than the 10 days expected and is a result of week-end gaps in the time-point sequence.