Table of Contents

Composite indicators

An indicator can be built on top of other indicators: the inner indicators are calculated first, and their values go into your own calculation. The built-in indicators (EMA, SMA, MACD and the others) live in the ATAS.Indicators.Technical namespace of the ATAS.Indicators.Technical assembly, which is part of the SDK package.

Calculating an inner indicator yourself

The Calculate(bar, value) extension method (class ATAS.Indicators.Extensions) writes value into the source series of the inner indicator for the bar, calculates it and returns its value for this bar. A MACD built from two EMAs and an SMA:

using System.Windows.Media;

using ATAS.Indicators;
using ATAS.Indicators.Technical;
using OFT.Rendering.Settings;

namespace ATAS.Docs.Samples.ChartIndicators;

public class MacdCalculateIndicator : Indicator
{
    private readonly EMA _long = new();
    private readonly EMA _short = new();
    private readonly SMA _sma = new();

    public int LongPeriod
    {
        get => _long.Period;
        set
        {
            if (value <= 0)
                return;

            _long.Period = value;
            RecalculateValues();
        }
    }

    public int ShortPeriod
    {
        get => _short.Period;
        set
        {
            if (value <= 0)
                return;

            _short.Period = value;
            RecalculateValues();
        }
    }

    public int SignalPeriod
    {
        get => _sma.Period;
        set
        {
            if (value <= 0)
                return;

            _sma.Period = value;
            RecalculateValues();
        }
    }

    public MacdCalculateIndicator()
    {
        Panel = IndicatorDataProvider.NewPanel;
        ((ValueDataSeries)DataSeries[0]).VisualType = VisualMode.Histogram;
        ((ValueDataSeries)DataSeries[0]).Color = Colors.CadetBlue;
        DataSeries.Add(new ValueDataSeries("Signal")
        {
            VisualType = VisualMode.Line,
            LineDashStyle = LineDashStyle.Dash
        });

        LongPeriod = 26;
        ShortPeriod = 12;
        SignalPeriod = 9;
    }

    protected override void OnCalculate(int bar, decimal value)
    {
        var macd = _short.Calculate(bar, value) - _long.Calculate(bar, value);
        var signal = _sma.Calculate(bar, macd);
        this[bar] = macd;
        DataSeries[1][bar] = signal;
    }
}

The inner indicators keep their own state from bar to bar, so call Calculate for every bar in ascending order, unconditionally, from your OnCalculate; skipping bars or calling it out of order gives wrong values.

Internal indicators

When an inner indicator takes the same source as your indicator, add it with Add in the constructor instead. An added indicator gets the same source data series and data provider as yours, is recalculated together with it (before your OnCalculate is called for the bar) and is disposed together with it. Its values are then read by bar index:

using System.Windows.Media;

using ATAS.Indicators;
using ATAS.Indicators.Technical;
using OFT.Rendering.Settings;

namespace ATAS.Docs.Samples.ChartIndicators;

public class MacdInternalIndicator : Indicator
{
    private readonly EMA _long = new();
    private readonly EMA _short = new();
    private readonly SMA _sma = new();

    public int LongPeriod
    {
        get => _long.Period;
        set
        {
            if (value <= 0)
                return;

            _long.Period = value;
            RecalculateValues();
        }
    }

    public int ShortPeriod
    {
        get => _short.Period;
        set
        {
            if (value <= 0)
                return;

            _short.Period = value;
            RecalculateValues();
        }
    }

    public int SignalPeriod
    {
        get => _sma.Period;
        set
        {
            if (value <= 0)
                return;

            _sma.Period = value;
            RecalculateValues();
        }
    }

    public MacdInternalIndicator()
    {
        Panel = IndicatorDataProvider.NewPanel;

        ((ValueDataSeries)DataSeries[0]).VisualType = VisualMode.Histogram;
        ((ValueDataSeries)DataSeries[0]).Color = Colors.CadetBlue;

        DataSeries.Add(new ValueDataSeries("Signal")
        {
            VisualType = VisualMode.Line,
            LineDashStyle = LineDashStyle.Dash
        });

        LongPeriod = 26;
        ShortPeriod = 12;
        SignalPeriod = 9;

        Add(_short);
        Add(_long);
    }

    protected override void OnCalculate(int bar, decimal value)
    {
        var macd = _short[bar] - _long[bar];
        var signal = _sma.Calculate(bar, macd);

        this[bar] = macd;
        DataSeries[1][bar] = signal;
    }
}

Using the data series of an inner indicator

An internal indicator with several DataSeries exposes all of them. The Signal series of the built-in MACD read from another indicator:

using ATAS.Indicators;
using ATAS.Indicators.Technical;

namespace ATAS.Docs.Samples.ChartIndicators;

public class MacdSignalConsumerIndicator : Indicator
{
    private readonly MACD _macd = new();

    public MacdSignalConsumerIndicator()
    {
        Add(_macd);
    }

    protected override void OnCalculate(int bar, decimal value)
    {
        var macdSignalSeries = (ValueDataSeries)_macd.DataSeries[1];
        var macdSignal = macdSignalSeries[bar];

        this[bar] = macdSignal * 2;
    }
}

To show a series of an inner indicator on the chart, add it to the DataSeries collection of your indicator:

using ATAS.Indicators;
using ATAS.Indicators.Technical;

namespace ATAS.Docs.Samples.ChartIndicators;

public class MacdSignalDisplayIndicator : Indicator
{
    private readonly MACD _macd = new();

    public MacdSignalDisplayIndicator()
    {
        Add(_macd);
        DataSeries.Add(_macd.DataSeries[1]);
    }

    protected override void OnCalculate(int bar, decimal value)
    {
    }
}