Nesting Series Functions
This section highlights another shortcoming of simple stateless series functions. They return individual values, which cannot be used as argument to other series functions. What if we need to calculate an average of a previously calculated average. The Domain Specific Language Extension (DSL), covered in the next section, makes this easy since all DSL functions return references to internally managed series which allow nested function calls like:
auto avg_of_avg = SMA(SMA(data, period), period);
This also works for 'stateful' functions, such as an exponential moving average:
auto avg_of_avg = EMA(EMA(data, period), period);
Implementing such functionality using non-DSL code can be very tedious. It involves declaring series for storing interim results, which can then be passed to subsequent functions, as well as handling series-bounds-errors.
So how do we calculate an average of an average without using DSL? Tutorial 117 shows how:
(1)
(2)
(3)
(4)
|
#include "tsa.h" #include "tsa-graphics.h"
using namespace tsa;
class my_strategy : public strategy { in_stream ser; chart ch;
void on_start(void) { ser.as_random_walk(); }
series<double> ma10;
void on_bar_close(void) override { catch_series_bounds_errors(true);
double avg = average(ser.close, 10);
ma10.push(avg); // interim series
double avg_of_avg = average(ma10, 10); // average of average
catch_series_bounds_errors(false);
ch << chart::pane(500) << plot::ohlc(ser) << ":RAND-WALK" << plot::line(avg) << ":AVG" << plot::line(avg_of_avg) << ":AVG-OF-AVG"; } };
my_strategy s; s.name("117_basic"); s.output_base_path(os::path("output")); s.enable_reports(); s.auto_open_reports(); s.run("2010-02-01", "2012-03-01"); |
Program Output |
|
(1) |
series<double> ma10;
Declare a series member. |
(2)
(3) |
double avg = average(ser.close, 10);
ma10.push(avg);
Calculate the first average and pushes the return value onto our series member which servers as a temporary storage. |
(4) |
double avg_of_avg = average(ma10, 10);
Calculates the average of the average. |
The above method is tedious because it throws series-bounds-error exceptions in two places. First, when calculating the first average in (1), and second, when calculating the second average in (4). This requires the call to the strategy's catch_series_bounds_errors(). Recall that this enables the strategy to silently handle series-bounds-error simply by moving on to the next strategy interval (bar).