Self Referential Series
DSL allows users to manipulate series, but not individual values! There is, however, one exception. It is possible to add values to a special type of series one value at a time. This feature is necessary so that series can be self-referential. The type of series that allows this is class self_ref_series<T>. A simple rolling sum would be declared as
void on_prepare_dsl(void) override
{
self_ref_series<double> rolling_sum(0.0); // creates self referential series, init to zero
rolling_sum = rolling_sum + 1.0; // add 1.0 to existing value
DPRINT(rolling_sum);
}
The ability to be self-referential is important as some indicators are defined in terms of self. For example, an exponential moving average function would be defined as:
sref<double> EMA_DEMO(sref<double> ser, size_t period)
{
verify_non_zero(period);
self_ref_series<double> ema(0.0); // creates self referential series, init to zero
auto weight = CONST(20.0 / (period + 1.0));
ema = (ser * weight) + (1.0 - weight) * ema; // new value is function of previous value of self
return ema;
}
The way this works is that self_ref_series<T> creates an internally managed function object initialized to a certain value. When the self_ref_series<T> object goes out of scope, the internally managed function object survives and will keep track of the object's value. class self_ref_series<T>'s assignment operator allows object binding in such a way that self a referential relationship exists. DSL does otherwise not allow self referential relationships. Another thing to know is :
Assignment operator=() can only be called *ONCE* on a given self_ref_series<T> object!
for example:
self_ref_series<double> ema(0.0);
ema = ema + 1; //assign once only!
ema = ema + 2; //error!
ema = ema + 3; //error!
Examples of Self Referential Functions
series_tuple<double> BANDPASSFILTER(sref<double> data, size_t period, double bandwidth)
{
verify_non_zero(period);
verify_non_zero(bandwidth);
self_ref_series<double> HP(0.0);
self_ref_series<double> BP(0.0);
self_ref_series<double> peak(0.0);
self_ref_series<double> trigger(0.0);
auto alpha2 = (COS(0.25*bandwidth * 2.0 * PI() / period)
+ SIN(0.25*bandwidth * 2.0 * PI() / period) - 1.0) / COS(0.25*bandwidth * 2.0 * PI() / period);
HP = (1.0 + alpha2 / 2.0)*(data - data(1)) + (1.0 - alpha2)*HP;
auto beta1 = COS(2.0 * PI() / period);
auto gamma1 = 1.0 / COS(2.0 * PI() * bandwidth / period);
auto alpha1 = gamma1 - SQRT(gamma1*gamma1 - 1.0);
BP = 0.5*(1.0 - alpha1)*(HP - HP(2)) + beta1*(1.0 + alpha1)*BP - alpha1*BP(1);
peak = IF(ABS(BP) > 0.991*peak, ABS(BP), 0.991*peak);
auto signal = SAFE_DIVIDE(BP, peak);
alpha2 = (COS(1.5*bandwidth * 2.0 * PI() / period)
+ SIN(1.5*bandwidth * 2.0 * PI() / period) - 1.0) / COS(1.5*bandwidth * 2.0 * PI() / period);
trigger = (1.0 + alpha2 / 2)*(signal - signal(1)) + (1.0 - alpha2)*trigger;
series_tuple<double> tuple = {
signal.name("Signal").plot_as("Signal", 0, plot_type::line, color::white),
trigger.name("Trigger").plot_as("Trigger", 0, plot_type::line, color::yellow)
};
return tuple;
}
sref<double> LAGUERREMA(sref<double> data, double gamma)
{
verify_non_zero(gamma);
self_ref_series<double> L0(0.0);
self_ref_series<double> L1(0.0);
self_ref_series<double> L2(0.0);
self_ref_series<double> L3(0.0);
self_ref_series<double> filt(0.0);
L0 = (1.0 - gamma)*data + gamma*L0;
L1 = -gamma*L0 + L0(1) + gamma*L1;
L2 = -gamma*L1 + L1(1) + gamma*L2;
L3 = -gamma*L2 + L2(1) + gamma*L3;
filt = (L0 + 2.0 * L1 + 2.0 * L1 + L3) / 6.0;
filt.plot_as("Laguerre MA", 0, plot_type::line, color::cadet_blue, 2);
return filt;
}