Индикатор с несколькими вычислениями

В TSLab у блоков есть ограничение, блок может выдавать только одно значение. Бывают ситуации, когда один индикатор должен рассчитывать несколько значений и все их нужно использовать в скрипте.

Например, мы хотим написать индикатор Боллинжера, причем в скрипте мы хотим использовать верхнюю линию, нижнюю линию и ширину канала. Для расчета этих значений достаточно рассчитать среднюю линию SMA и стандартное отклонение StDev.

Напишем универсальный блок MyBollinger, который может в зависимости от параметров выдавать верхнюю линию, нижнюю линию или ширину боллинжера. У блока будут параметры:

  • Period – это период расчета индикатора.

  • Coef – это коэффициент расчета индикатора

  • Type – это тип перечисления задающий способ расчета индикатора. Если Top, то выводит верхнюю линию. Если Bottom, то выводит нижнюю линию. Если Width, то выводит ширину канала.

Формулы такие: Верхняя линия: SMA + StDev * Coef. Нижняя линия: SMA - StDev * Coef. Ширина канала: верхняя линия - нижняя линия.

Расчеты индикаторов SMA и StDev мы сделаем через кеширование (Context.GetData). Это сократит количество одинаковых расчетов. Например, в скрипт добавим один блок выдающий верхнюю линию, один блок выдающий нижнюю линию и один блок выдающий ширину канала. При выполнении скрипта первый блок рассчитает значения SMA, StDev и запишет их в кеш. Потом второй и третий блоки уже не будет рассчитывать эти значения, они просто возьмут их из кеша.

Важно: кешировать индикаторы нужно с полной цепочкой зависимых параметров (подробнее тут). Для этого добавлен интерфейс INeedVariableParentNames, у него есть одно свойство VariableParentNames, которое содержит названия родительских блоков с параметрами. Это свойство добавим во все кеши рассчитываемых индикаторов, для того чтобы точно идентифицировать входящий список IList<double> values.

using System.Collections.Generic;
using System.ComponentModel;
using TSLab.Script.Handlers;
using TSLab.Script.Helpers;

namespace MyLib
{
    [HandlerCategory("MyLib")]
    [HandlerName("MyBollinger")]
    [Description("Рассчитывает верхнюю, нижнюю линию или ширину боллинжера")]
    [InputsCount(1)]
    [Input(0, TemplateTypes.DOUBLE)]
    [OutputsCount(1)]
    [OutputType(TemplateTypes.DOUBLE)]
    public class MyBollinger : IStreamHandler, IContextUses, INeedVariableParentNames
    {
        public enum MyBollingerType
        {
            Top,
            Bottom,
            Width
        }

        [HandlerParameter(true, "20", Min = "1", Max = "100", Step = "1")]
        public int Period { get; set; }

        [HandlerParameter(true, "2", Min = "1", Max = "4", Step = "0.1")]
        public double Coef { get; set; }

        [HandlerParameter(true, nameof(MyBollingerType.Top))]
        public MyBollingerType Type { get; set; }

        public IContext Context { get; set; }

        /// <summary>
        /// Тут будет список наименований родительских блоков с параметрами. В виде строки.
        /// Нужно для правильного кеширования индикаторов SMA и StDev.
        /// </summary>
        public string VariableParentNames { get; set; }

        public IList<double> Execute(IList<double> values)
        {
            // Получить скользящую среднюю (SMA)
            var sma = Context.GetData("SMA", new[] { VariableParentNames, Period.ToString() },
                () => GetSma(Context, values, Period));

            // Получить стандартное отклонение (StDev)
            var stdev = Context.GetData("StDev", new[] { VariableParentNames, Period.ToString() },
                () => GetStDev(Context, values, Period));

            var array = Context.GetArray<double>(values.Count);
            switch (Type)
            {
                case MyBollingerType.Top:
                    // Если считаем верхнюю линию боллинжера, то складываем: SMA + StDev * Coef.
                    for (int i = 0; i < values.Count; i++)
                        array[i] = sma[i] + stdev[i] * Coef;
                    break;

                case MyBollingerType.Bottom:
                    // Если считаем нижнюю линию боллинжера, то вычитаем: SMA - StDev * Coef.
                    for (int i = 0; i < values.Count; i++)
                        array[i] = sma[i] - stdev[i] * Coef;
                    break;

                case MyBollingerType.Width:
                    // Если считаем ширину канала, то из верхней линии боллинжера вычитаем нижнюю.
                    for (int i = 0; i < values.Count; i++)
                        array[i] = (sma[i] + stdev[i] * Coef) - (sma[i] - stdev[i] * Coef);
                    break;
            }
            return array;
        }

        public static IList<double> GetSma(IContext ctx, IList<double> values, int period)
        {
            // Выводим в лог сколько раз вызвался этот метод при расчете скрипта.
            // Для одного боллинжера с одинаковыми параметрами будет только одни запуск этого метода.
            // Это только для демонстрации, в 'боевых' индикаторах вывод в лог не обязателен.
            ctx.Log($"Get sma", toMessageWindow: true);
            return Series.SMA(values, period, ctx);
        }

        public static IList<double> GetStDev(IContext ctx, IList<double> values, int period)
        {
            // Выводим в лог сколько раз вызвался этот метод при расчете скрипта.
            // Для одного боллинжера с одинаковыми параметрами будет только одни запуск этого метода.
            // Это только для демонстрации, в 'боевых' индикаторах вывод в лог не обязателен.
            ctx.Log($"Get stdev", toMessageWindow: true);
            return Series.StDev(values, period, ctx);
        }
    }
}

Last updated