Learn how to use data dependencies, the variable object, and how to deal with indicators with irregular periods.

Main data dependency

The data building procedure works under the premise that the algorithm loops around a data collection and that—for each loop—generates a record for the product being built. For example, the SMA indicator loops around the candles product of the Candles Volumes indicator, thus, candles is the main data dependency. In the case of the bollingerChannels product, the main data dependency is the bollingerBands product of the Bollinger Bands indicator.

The main data dependency is the first dependency declared at the level of the processes. This dependency is made available in the record object, which features three properties:

  • previous: mainDependency.records[index - 1]
  • current: mainDependency.records[index],
  • next: mainDependency.records[index + 1]

The variable index is the index in the array of the main dependency.

For example, you may use the statement record.propertyName to access the value of any of the properties in the input datasets for the current record, or record.previous.propertyName to access the value of the property in the previous record.

Multiple data dependencies

The products object features a property for each data dependency, and each property is named with the pluralVariableName defined in the product’s configuration. Each property features an array with the dependency’s data collection.

Data collections of different products may begin and end at different points in time. For example, it takes 20 candles to build the first bollingerBands object of the Bollinger Bands indicator, or 200 candles to build the first popularSMAs object of the SMA indicator. Therefore, the record in i position in the array of one product may not correspond to the same period of time of the record in the same position of a different product.

To overcome that hurdle, the getElement function, which takes two parameters, may be used:

  • The name of the data collection (pluralVariableName) you wish to consult.

  • An object with begin and end properties, for example, the tenth candle.

The function returns the record that matches the begin and end datetime of the second parameter.

This is an example from the Candle Patterns indicator, which has the candles product of the Candles Volumes indicator as the main data dependency:

let candle = record.current                                      // Calling "Candles"
let basicCandle = getElement ('basicCandles', candle)            // Calling "Basic Candles"

Variable object

The output generated must be stored as new properties of the variable object, which is made available to record properties under the record definition node.

In the case of the data building procedure loop, the variable object may also be used to store and—later—retrieve information between different loop cycles, or even between different executions of the indicator process. Such a feature is not available in the calculations procedure loop, as the object does not persist beyond each loop in that context.

Multi-Period-Daily

In time frames below one hour, the system allows to access data up to 48 hours in the past, relative to the current record. If more than 48-hours worth of data is required to produce the desired data building, then data must be stored into the variable object to be retrieved later on, as explained above.

The following is an example of a procedure loop code, in particular, the code that calculates the Popular SMAs product of the Simple Moving Average (SMA) indicator. Notice how the output generated by the calculateSMA function is stored into the variable object, and how an array stored in the variable object is used to overcome the 48-hours data-access limitation of Multi-Priod-Daily time frames.

/* Loop Code. */

let candle = record.current // Our main dependency is candles 
variable.last200.push(candle.close) // Add the current close value to the last 200 array.

if (variable.last200.length > 200) { // Store data in the variable object to overcome the limitations in multi-period-daily.
    variable.last200.splice(0, 1) // Remove the first element of the array to keep it at a maximun of 200 elements.
}

variable.sma20 = calculateSMA(20)
variable.sma50 = calculateSMA(50)
variable.sma100 = calculateSMA(100)
variable.sma200 = calculateSMA(200)

function calculateSMA(periods) {  // Having a function saves us from duplicating code.
    /* We check we have enough values to make the calculation */
    if (variable.last200.length < periods) { return 0 } // If we dont, we define the value is zero.

    let sum = 0 // Initialize sum variable. 
    for (let i = variable.last200.length - periods; i < variable.last200.length; i++) { // Iterate through the last periods
        sum = sum + variable.last200[i]
    }
    let sma = sum / periods
    return sma
}

Irregular periods

Occasionally, a one-to-one mapping of the periods of the data dependency with the periods of the resulting product does not exist. For example, in the case of the bollingerChannels product, using the bollingerBands as the main dependency. In such a case, the algorithm may read the 1-hour bollingerBands as input but the resulting object may span several hours.

By default, the system pushes a record into the data collection for every loop. To build products that span several periods of the main dependency, you must make the push from within your own code.

When the system detects a push in the procedure loop code, it cancels the automatic push on each loop.

The following example taken from the data building procedure loop of the Bollinger Channels Object indicator features several instances of arbitrary use of pushes.


if (record.next !== undefined) {

    if (
        record.current.direction === record.next.direction) {

        if (variable.channel === undefined) {

            variable.channel = {
                begin: undefined,
                end: undefined,
                direction: undefined,
                period: 0,
                firstMovingAverage: 0,
                lastMovingAverage: 0,
                firstDeviation: 0,
                lastDeviation: 0
            }

            variable.channel.direction = record.current.direction
            variable.channel.period = 2

            variable.channel.begin = record.current.begin
            variable.channel.end = record.next.end

            variable.channel.firstMovingAverage = record.current.movingAverage
            variable.channel.lastMovingAverage = record.next.movingAverage

            variable.channel.firstDeviation = record.current.deviation
            variable.channel.lastDeviation = record.next.deviation

        } else {

            variable.channel.period++
            variable.channel.end = record.next.end
            variable.channel.lastMovingAverage = record.next.movingAverage
            variable.channel.lastDeviation = record.next.deviation

        }
    } else {

        if (variable.channel !== undefined) {
            results.push(variable.channel)
            variable.channel = undefined
        } else {
            /* The variable.channel has only one period */

            variable.channel = {}

            variable.channel.direction = record.current.direction
            variable.channel.period = 1

            variable.channel.begin = record.current.begin
            variable.channel.end = record.current.end

            variable.channel.firstMovingAverage = record.current.movingAverage
            variable.channel.lastMovingAverage = record.current.movingAverage

            variable.channel.firstDeviation = record.current.deviation
            variable.channel.lastDeviation = record.current.deviation

            results.push(variable.channel)
            variable.channel = undefined
        }
    }
} else {
    if (variable.channel !== undefined) {
        variable.channel.period++
        variable.channel.end = record.current.end
        variable.channel.lastMovingAverage = record.current.movingAverage
        variable.channel.lastDeviation = record.current.deviation
        results.push(variable.channel)
        variable.channel = undefined
    } else {
        /* The variable.channel has only one period */

        variable.channel = {}

        variable.channel.direction = record.current.direction
        variable.channel.period = 1

        variable.channel.begin = record.current.begin
        variable.channel.end = record.current.end

        variable.channel.firstMovingAverage = record.current.movingAverage
        variable.channel.lastMovingAverage = record.current.movingAverage

        variable.channel.firstDeviation = record.current.deviation
        variable.channel.lastDeviation = record.current.deviation

        results.push(variable.channel)
        variable.channel = undefined
    }
}

Managing undefined objects

In many instances, objects may be undefined. For example, the first 199 candles in the data collection corresponding to a 200-period moving average may be undefined, as the data building procedure may only build the object once the 200th candle is evaluated.

Storing undifined property values is highly undesirable, as that would likely produce errors downstream when others attempt to consume the products produced by the indicator. How to handle such circumstances is up to the developer.

The Bollinger Channels Object example above features deliberate handling of undefined objects.

In the case of a 200-period moving average discussed above, the developer may want to produce an arbitrary estimation of the moving average for the first 199 candles, or simply set the value to zero for the first 199 candles. The later was the choice for the popularSMAs product as may be observed in the code of the initialization procedure:

/* Initialization Code*/

variable.previousEMA20 = 0
variable.previousEMA50 = 0
variable.previousEMA100 = 0
variable.previousEMA200 = 0