Functors (Function Objects)


'Stateless' functions are not suitable for certain algorithms that require 'state' to be maintained between strategy bars. One such function is the 'exponential moving average' as well as the majority of technical indicators commonly used today. The best way to implement stateful functionality is to use the Domain Specific Language (DSL) extension.

For the sake of completeness we will take a look at how this is done using function objects (functors). This has further relevance since DSL itself heavily relies on such 'functors' internally . We will thus encounter them again in Chapter 3 on DSL.

 

 

A 'function class' defines the function call operator(). A simple rolling-sum functor could look like this:

 

class rolling_sum{

                    public:

                              double sum = 0.0;

                              operator()(double latest_value)

                              {

                                        sum += latest_value;

                              }

};

 

Such an object would need to be added as a strategy member, and updated once per bar. The functor above doesn't achieve anything that a simple member variable couldn't do just as well. However, functors can be very useful for complex financial market analytics. For regular technical indicators you will be better off using the Domain Specific Language (DSL) extension as it was developed specifically for that purpose. There are situations however where DSL is not the right tool. For example, you may require a class that performs price 'swing' analysis, such as required for Elliot Wave Analysis. Because Elliot Waves are fractal, with any number of fractal dimensions, the analysis is quite complex. A class performing such analysis would have to maintain a myriad of arrays of various structures. Furthermore, the return value is not a single value or series, but rather users would likely require a set of accessor functions to retrieve detailed information about the current wave count. With such complex requirements it is best to define a functor class independent of the DSL.

 

Tutorial 118 presents a functor class for calculating an exponential moving average. 

 

 

 

 

 

 

 

(1)

 

 

 

 

 

(2)

 

 

 

 

 

 

 

 

 

 

(3)

 

 

 

 

 

 

 

 

 

 

(4)

 

 

 

 

 

(5)

 

 

 

(6)

 

 

 

 

 

 

 

#include "tsa.h"

#include "tsa-graphics.h"

 

using namespace tsa;

 

 

          class exp_avg_functor // exponential moving average 'functor'

          {

                    size_t m_period;

                    double m_exp_avg, m_mult;

                    bool m_is_init = false;

          public:

                    void operator()(const series<double>& ser, size_t period)

                    {

                              if (!m_is_init){

                                        ser.verify_size(m_period);

                                        m_period = period;

                                        m_exp_avg = average(ser, m_period); // init start value

                                        m_mult = 2.0 / ((double)m_period + 1.0);

                                        m_is_init = true;

                              }

                              m_exp_avg = (m_mult * ser[0]) + (1.0 - m_mult) * m_exp_avg;

                    }

                    double value(void)const { return m_exp_avg; }

          };

 

 

          class my_strategy : public strategy

          {

                    in_stream mkt;

                    chart ch;

 

                    void on_start(void) override { mkt.as_random_walk(); }

 

                    exp_avg_functor ema;         // declaring 'functor' member

 

                    void on_bar_close(voidoverride

                    {

                              catch_series_bounds_errors(true);

 

                              ema(in.close, 10);     // invoking 'functor' object

 

                              ch << chart::pane(450)                     << "Pane Title"

                                        << plot::ohlc(mkt, color::gold)       << "Random prices"

                                        << plot::line(ema.value())            << "EMA-10"

                                        << plot::line(average(mkt.close,10))  << "SMA-10";

                    }

          };

 

          my_strategy s;

 

          s.output_base_path("output");

          s.run("2012-01-01", "2012-12-30");

 

Program Output:

 

 

(1)

class exp_avg_functor

          

Declares a new class. There is nothing special about it. It doesn't inherit from any other class. To use it as a stateful function (functor)  we only need a way to pass the data and period arguments, and we also need a way to retrieve the calculated value.

(2)

To pass arguments we define the 'function call' operator(). Doing so is however not a requirement - you can call the member function anything you like. 

 

                    void operator()(const series<double>& ser, size_t period)

                    {

                              if (!m_is_init){

                                        ser.verify_size(m_period);

                                        m_period = period;

                                        m_exp_avg = average(ser, m_period); //init start value

                                        m_mult = 2.0 / ((double)m_period + 1.0);

                                        m_is_init = true;

                     return;

                              }

                              m_exp_avg = (m_mult * ser[0]) + (1.0 - m_mult) * m_exp_avg;

                    }

 

This functor definition is relatively straight forward. An exponential moving average is calculated by looking at the value it had on the previous bar. For this to work, this value needs to be initialized on the first bar. We use the regular average() function to perform this initialization, but only when the data series has enough data - hence the call to verify_size(). One could initialize the value to 0 but the resulting data would look awkward on charts as the calculated value would start near zero, regardless of the values in our data series.

 

        m_exp_avg = average(ser, m_period); //init start value

 

On subsequent bars the formula is simply:

 

        m_exp_avg = (m_mult * ser[0]) + (1.0 - m_mult) * m_exp_avg;

 

This formula gives more weight to the most recent values, and so an exponential average responds much faster to any changes in price than the regular simple moving average, as can be seen on the output chart above.

(3)

The calculated value is accessed via the value() member:

 

        double value(void)const { return m_exp_avg; }

(4)

Here we declare our functor strategy member:

 

exp_avg_functor ema;

(5)

Here we call our functor - once per bar:

 

   ema(in.close, 10);

 

Warning: One may be tempted to call the 'functor' repeatedly on the same bar. This is not appropriate. On any one particular bar, a functor must be called once only. If you need to track multiple exponential averages, you need to declare and use multiple functor objects.

This is why you should refrain from declaring operator() with a return value, as that may confuse the issue even further.

(6)

Plots both the exponential moving average as well as the symmetric moving average. The result is the chart shown as program output above. Note how the exponential moving average is far more responsive to recent price changes.