4.5 Functions and Special Variables

Functions

Simple functions execute some operation over the sent arguments and return the results. If the arguments are identical, the result will always be the same. These are so-called “pure” functions. Examples of these functions are: Math.Max(), Highest() etc. But there are functions, which store certain information, for example, results of previous calculations. We can take XAverage (Exponential Moving Average) as an example, where the current value is calculated using the previous value of the function and the arguments. These are so-called functions with state or functional objects. Class-functions, inherited from FunctionSeries<T> or FunctionSimple<T> are such functional objects.

In PL.NET there is a variety of such built-in functional objects. The standard model of their usage is the creation of an instance of the function object, initializing it with actual arguments and addressing the function value during the calculation process.

Variables

Those, who are familiar with C# or other OOP programming languages know such things as class fields. Together they form the state of the object of the given class. In PL.NET there are special classes of variables which make the writing logic of functions, indicators and signals easier regardless of whether the calculation is executed based on historical or real-time data. When calculating a study on historical data, CalcBar() method will be called once for each bar. This is an example of such a version of CurrentBar:

public class CurrentBar : FunctionSimple<int>
    {
        public CurrentBar(CStudyControl master) : base(master){}
        private int m_current_bar;
        protected override int CalcBar(){
            return ++m_current_bar;
        }
        protected override void StartCalc(){
            m_current_bar = 0;
        }
}

When calculating on historical data this function will return correct values only if it is called once per bar. In real-time calculation, CalcBar will be called on each tick of bar, the function will not work correctly. To make it work correctly, its logic should be changed. For example:

protected override int CalcBar(){
            var _result = m_current_bar + 1;
            if (Bars.Status==EBarState.Close)
                ++m_current_bar;
            return _result;
        }

As we can see, the value of the variable has to be incremented only at the bar close. For these cases specific variables exist: VariableSeries<T> and VariableObject<T>. VariableSeries<T> should be used when it is necessary to call for the values of the previous bars variable. Considering this, the example would look like this:

public class CurrentBar : FunctionSimple<int>
    {
        public CurrentBar(CStudyControl master) : base(master){}
 
        private VariableObject<int> m_current_bar;
        protected override int CalcBar(){
            return ++m_current_bar.Value;
        }
        protected override void Create(){
            m_current_bar = new VariableObject<int>(this);
        }
    }

And it will equally work either at historical calculation, or real-time calculation. In addition to this, it will also work correctly, even if we call this function several times during the bar. In summarizing the information about functions and variables, our example with CurrentBar can be simplified:

public class CurrentBar : FunctionSeries<int>
    {
        public CurrentBar(CStudyControl master) : base(master){}
 
        protected override int CalcBar(){
            return this[1] + 1;
        }
    }

Barsback

Barsback is the offset on the left of the price data series measured in bars and is necessary for the study calculation.

Let’s consider the following example:

using System;
 
namespace PowerLanguage.Indicator
{
    public class ITest : IndicatorObject
    {
        private VariableSeries<Double> m_plotVal;
 
        private IPlotObject Plot1;
 
        public ITest(object ctx) : base(ctx) {}
 
        protected override void Create() {
            Plot1 = AddPlot(new PlotAttributes("Test"));
            m_plotVal = new VariableSeries<Double>(this);
        }
 
        protected override void CalcBar() {
            m_plotVal.Value = Bars.Close[10] - Bars.CloseValue;
            Plot1.Set(0, m_plotVal.Value);
        }
    }
}

This simple example shows, that to calculate this study, at least eleven bars of the price data series are necessary (Bars.Close[10] call considers Close price removal ten bars back from the current calculation bar).

Barsback can be set by the user manually (in indicator properties) or it can be calculated automatically. In this simple case, the maximum quantity of bars back =11, can be set manually. It is not always clear to the user how many bars of price data series it should be used, in this case it is best to have it automatically calculated. Let’s change the initial example:

using System;
 
namespace PowerLanguage.Indicator
{
    public class ITest : IndicatorObject
    {
        private VariableSeries<Double> m_plotVal;
        private IPlotObject Plot1;
 
        public ITest(object ctx) : base(ctx)
        {
            length = 10;
        }
 
        [Input]
        public int length { get; set; }
 
        protected override void Create()
        {
            Plot1 = AddPlot(new PlotAttributes("Test"));
            m_plotVal = new VariableSeries<Double>(this);
        }
 
        protected override void CalcBar() {
            m_plotVal.Value = Bars.Close[length] - Bars.CloseValue;
            Plot1.Set(0, m_plotVal.Value);
        }
    }
}

It is not known in advance which input a user will set. But the number of data series bars, necessary for the indicator calculation, depends on it (determined by Bars.Close[length] expression). In this case, you can select the Auto-detect option for the “Max number of bars study will reference” setting. Then Barsback for the study will be selected automatically during the calculation. It works as follows:

  1. During the first calculation by default it is considered that one bar is enough for the calculation.
  2. When, during the calculation at the first bar Bars.Close[length] (length > 0) expression is executed, an exception will be generated inside the calculation module causing the study to stop the calculation (StopCalc).
  3. The exception is intercepted and according to the specific algorithm, barsback value is incremented. Then, the study is initialized (StartCalc) and the new calculation starts (from the bar with the barsback number, which is the new value of the minimum number of bars, necessary for the calculation).
  4. If at the execution of Bars.Close[length] expression the quantity of bars is not sufficient (length > barsback), an exception will be generated, the study stops the calculation (StopCalc). Then, it will go to the point 3. And so on, until the quantity of bars is enough.

Note. If the built data series does not have the necessary quantity of bars, the indicator does not switch off. It stays at Calculating mode… and waits, until the quantity of bars of the data series is sufficient.