В 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.
usingSystem.Collections.Generic;usingSystem.ComponentModel;usingTSLab.Script.Handlers;usingTSLab.Script.Helpers;namespaceMyLib{ [HandlerCategory("MyLib")] [HandlerName("MyBollinger")] [Description("Рассчитывает верхнюю, нижнюю линию или ширину боллинжера")] [InputsCount(1)] [Input(0,TemplateTypes.DOUBLE)] [OutputsCount(1)] [OutputType(TemplateTypes.DOUBLE)]publicclassMyBollinger:IStreamHandler,IContextUses,INeedVariableParentNames {publicenumMyBollingerType { Top, Bottom, Width } [HandlerParameter(true,"20", Min ="1", Max ="100", Step ="1")]publicint Period { get; set; } [HandlerParameter(true,"2", Min ="1", Max ="4", Step ="0.1")]publicdouble Coef { get; set; } [HandlerParameter(true,nameof(MyBollingerType.Top))]publicMyBollingerType Type { get; set; }publicIContext Context { get; set; } /// <summary> /// Тут будет список наименований родительских блоков с параметрами. В виде строки. /// Нужно для правильного кеширования индикаторов SMA и StDev. /// </summary>publicstring VariableParentNames { get; set; }publicIList<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) {caseMyBollingerType.Top: // Если считаем верхнюю линию боллинжера, то складываем: SMA + StDev * Coef.for (int i =0; i <values.Count; i++)array[i] =sma[i] +stdev[i] * Coef;break;caseMyBollingerType.Bottom: // Если считаем нижнюю линию боллинжера, то вычитаем: SMA - StDev * Coef.for (int i =0; i <values.Count; i++)array[i] =sma[i] -stdev[i] * Coef;break;caseMyBollingerType.Width: // Если считаем ширину канала, то из верхней линии боллинжера вычитаем нижнюю.for (int i =0; i <values.Count; i++)array[i] = (sma[i] +stdev[i] * Coef) - (sma[i] -stdev[i] * Coef);break; }return array; }publicstaticIList<double> GetSma(IContext ctx,IList<double> values,int period) { // Выводим в лог сколько раз вызвался этот метод при расчете скрипта. // Для одного боллинжера с одинаковыми параметрами будет только одни запуск этого метода. // Это только для демонстрации, в 'боевых' индикаторах вывод в лог не обязателен.ctx.Log($"Get sma", toMessageWindow:true);returnSeries.SMA(values, period, ctx); }publicstaticIList<double> GetStDev(IContext ctx,IList<double> values,int period) { // Выводим в лог сколько раз вызвался этот метод при расчете скрипта. // Для одного боллинжера с одинаковыми параметрами будет только одни запуск этого метода. // Это только для демонстрации, в 'боевых' индикаторах вывод в лог не обязателен.ctx.Log($"Get stdev", toMessageWindow:true);returnSeries.StDev(values, period, ctx); } }}