Решил разобраться для себя в разделе «Циклы», а заодно и написать посты на форуме по каждому из имеющихся в ТСЛаб блоков из вышеуказанного раздела. Документация ТСЛаб по циклам размещена
здесь, в документации есть довольно подробные пояснения и даже примеры, но, судя по количеству вопросов, которые возникают по данным блокам в
группе, они далеко не всем понятны и не такие интуитивные как большинство других блоков, стало быть, требуют дополнительного исследования. Итак, начнем.
Блок «Цикл»Чтобы понимать, что такое итерация цикла необходимо ознакомиться с краткой
информацией по тому, что такое цикл. Фактически, цикл ТСЛаб представляет собой конструкцию цикла
For:
for ([действия_до_выполнения_цикла]; [условие]; [действия_после_выполнения])
{
// действия
}
В теле цикла конструкция производит какое-либо полезное для программы действие, которое повторяется энное количество раз, пока не будут окончены все итерации, либо пока не будет осуществлен выход из тела цикла.
У блока "Цикл" в ТСЛаб имеется только один вход. Как следует из документации, на данный вход подается какое-либо целое положительное число, которое управляет количеством итераций на каждой свече инструмента, то есть данный вход задает количество "запусков" кода из тела цикла на каждой свече инструмента. Дополнительно в цикле имеется настройка "Макс.Количество", которая также работает с количеством итераций и задает их максимальное значение. В совокупности получаем следующий результат - мы либо задаем кол-во итераций через вход кубика, либо непосредственно указываем это количество в настройке кубика если данное кол-во не предполагает изменяться в зависимости от каких-либо условий в скрипте.
Составим самую простую конструкцию и посмотрим какой именно код под нее создаст ТСЛаб. На входе зададим значение 5 (кубик "Iterations"), в настройках кубиках поставим максимальное значение 10. В случае если максимальное значение будет меньше того, что указано на входе, то будет использовано максимально значение.
В данный момент не будем разбираться почему конструкция именно такова, вернемся к этому моменту позже. Сейчас посмотрим что представляет из себя цикл и где именно в коде будут указанные 5 итераций на входе. Открываем сгенерированный файл и видим необходимую конструкцию.
В коде видим следующие инструкции:
int CycleCount = Cycle_h.GetCount(Iterations[i]);
Инструкция задает количество итераций цикла, при этом
Iterations[i] это и есть наш кубик "Iterations", который фактически представляет собой список констант по количеству загруженных в скрипт баров инструмента. ТСЛаб автоматически "разворачивает" все константы в списки значений, что можно видеть по следующему фрагменту кода.
public TSLab.Script.Optimization.OptimProperty Iterations_Value = new TSLab.Script.Optimization.OptimProperty(5D, false, 1D, 10D, 1D, 1);
this.Iterations_h.Value = ((double)(this.Iterations_Value.Value));
System.Collections.Generic.IList<double> Iterations = context.GetData("Iterations", new string[]
{
this.Iterations_h.Value.ToString(),
ТоргуемИнструм"
},
delegate {return this.Iterations_h.Execute(context);});
Метод
Cycle_h.GetCount(Iterations[i]) фактически представляет собой выбор минимального значения из двух возможных, то есть из значения, переданного на вход кубика и значения максимального количества итераций, указанного в настройках кубика. Далее это значение присваивается переменной
CycleCount и участвует непосредственно в установлении количества итераций сформированного далее цикла.
Цикл сформирован конструкцией "For Next Step", описанной выше в настоящем посте. На скрине синей рамкой обведен сам цикл, зеленой рамкой - тело цикла. Видим, что счетчик итераций
j начинается с нуля и будет увеличиваться на каждой итерации на 1,
максимальное значение будет равно
CycleCount-1 (в данном случае 4).
Итого получим 5 итераций на каждой свече инструмента, то есть
j будет увеличиваться от 0 до 4: 0, 1, 2, 3, 4.
У кубика как и большинства других кубиков снизу имеется контрольный выход, который можнт быть соединен с контрольной панелью для управления параметром "кол-во итераций". В информационных целях, думаю, что не лишним будет упомянуть, что не только контрольный выход с кубика может быть соединенен с контрольной панелью, но и основной выход, в этом случае на контрольную панель будет выведено последнее известное значение данного кубика на последнем баре скрипта. Это значение отображается в контрольной панели как поле, которое нельзя изменить (предназначено только для чтения).
Теперь рассмотрим верхний выход кубика. Согласно документации выход во-первых называется "служебный соединитель", а во-вторых имеет конкретное предназначение - соединиться с блоками блоками открытия позиций или обновляемыми значениями (простое и обновляемое значение цикла) для целей определения входа в цикл. Пока не очень понятно что это значит, посмотрим генерируемый код. Используем известный всем кубик "обновляемое значение", т.к. функционал "обновляемого значения цикла" пока не совсем понятен, разберем его позже. Тем, кто не очень хорошо знаком с функционалом "простого обновляемого значения" рекомендуется посмотреть
видео с канала "16:05 Algo", в котором хорошо объяснены все принципы и особенности работы с данным кубиком.
Для исследования подключения кубика "ОЗ" создадим в редакторе следующую конструкцию (для упрощения на вход "ОЗ" подадим значение кол-ва итераций, а в качестве условия обновления укажем
True с логической константы; в данном случае нам не важны поступающие на "ОЗ" значения, важен принцип работы данной конструкции).
Получим сгенерированный код, который расскажет для чего необходимо соединить "служебный соединитель" цикла с таким же выходом "ОЗ". Фактически по коду мы видим, что все, что в данной конструкции позволило нам соединение - это фактически создать цикл с пустым телом, то есть никакой полезной работы такая конструкция не выполняет - все итерации не будут делать ничего. Пока не совсем понятно для чего это нужно, разбираемся дальше.
Чтобы понять дальнешую логику несколько модернизируем скрипт, а именно прикрутим к циклу кубики "Формула" и "Результат цикла".
Кубик "Формула" и так всем знаком, а по кубику "Результат цикла" стоит дать пояснения. Согласно документации данный кубик считается окончанием цикла и содержит значение тела цикла, то есть результаты вычисления цикла на указанной итерации. Чтобы было понятно о чем речь, к примеру, у нас имеется счетчик, который на первой итерации каждого бара выдает 1, на второй 2, на третьей 3 и т.д. Задавая нужное значение на входе кубика "результат цикла" мы как раз и будем управлять тем значение, которое мы хотим получить. Есть один нюанс - нумераци итераций, как видно выше из конструкции цикла начинается с нуля, а не с единицы, соответственно, чтобы задать нужный номер итерации надо также начинать с нуля, то есть нужна итерация номер 1 - указываем на входе ноль, нужна итерация 2 - указываем на входе 1 и т.д.
В дополнение кубик имеет настройки "Индекс" и "Использовать последний индекс". Название настроек говорит само за себя: "Индекс" - задает номер итерации с которой необходимо получить значение, настройка "Использовать последний индекс" указывает на необходимость использовать последнюю итерацию, причем настройка "Использовать последний индекс" будет иметь приоритет над "Индекс", то есть, к примеру, если в цикле всего 5 итераций, в настройке "Индекс" задано значение 2, и активирована настройка "Использовать последний индекс", то будет выдано значение с пятой итерации несмотря на то, что в настройке "Индекс" указано значение 2. Происходит это потому что в коде условие "Использовать последний индекс" идет раньше, чем условие, заданное в настроке "Индекс" и если оно равно True, то дальнейшая проверка условия выполняться не будет, то есть будет выдано значение с последней итерации.
if ((CycleResult_h.UseLastIndex || (CycleResult_h.Index == j)))
Также есть еще один дополнительный нюанс - если на вход управления номеров итерации подключен кубик с номером как в примере ниже (кубик "Константа"), то будет использоваться только условие с этого кубика, условия заданные в кубике "Результат цикла" будут проигнорированы.
if ((((int)(IterNumber[i])) == j))
Проверим по коду изменилось ли что-либо в части обработки кубика "ОЗ", ведь он подключен к циклу и его значения фактически используются в расчетах, то есть данные выведены на панель графика и изменяются в формуле, которая, в свою очередь, также выведен на панель.
Как видно из кода - ничего не поменялось, то есть код обработки "ОЗ" располагается после цикла и никак в нем не задействован. Хм.. также не может быть, чтобы возможность подключения есть, но она ни на что не влияет. Пробуем еще изменить ранее собранную конструкцию - добавим выходящее из "ОЗ" значение в тело обработки цикла и посмотрим что из этого получится. Тут надо сделать оговорку относительно фразы "ни на что не влияет", как было описано выше (и описано в документации) данный вход используется для обозначения входа в цикл, то есть фактически данное соединение используется для того, чтобы создать саму конструкцию цикла и разместить ее в коде. Но можно ли как-то использовать само значение "ОЗ" в данном коде. "Да" - можно, причем для этого есть 2 варианта: первый вариант - подать данное значение от "ОЗ" в цепочку связей между циклом и его кубиком "Результат цикла"; второй вариант - разорвать цепочку и подать значение сразу в результат цикла, а обращение к итерации цикла подать из кубика "Цикл" на вход №2 кубика "Результат цикла", причем сделать это надо через формула, т.к. если подать напрямую, то связь соединится, но редактором будет выдана ошибка о том, что соединять кубик "цикл" с другими кубиками, кроме формул нельзя (Элемент 'Cycle' содержит ошибку: Выход обработчика "Цикл" может быть соединён только с формулами). Примеры и сгенерированный код ниже.
Способ №1У наблюдательного читателя может возникнуть разумный вопрос: что за мутная инструкция
UpdatableValue*(Cycle+1)/(Cycle+1) в формуле после цикла. Дело в том, что номер итерации цикла должен быть каким-либо образом использован в последующей формуле, иначе цикл не "заведется" и будет пытаться присвоить значение текущей итерации кубика цикла за пределами самого цикла, от чего редактор будет при запуске скрипта выбрасывать ошибку "CS0103: Имя "j" не существует в текущем контексте.". Но что если нам не нужен номер итерации в наших вычислениях, тогда разумно умножить значение в формуле на номер итерации и тут же разделить, получим значение формулы без изменений, то есть умноженное на 1, однако, т.к. итерации начинаются с нуля, то если просто указать
Cycle/Cycle, то получим деление на ноль, т.к. на первой же итерации запись превратится в 0/0, чтобы не получить такой результат прибавляем по единице, в этом случае не первой итерации получим значение (0+1)/(0+1), то есть 1.
Способ №2В общем и целом с блоком "Цикл" в его самом простом использовании имхо появилась некоторая ясность, в дальнейших постах рассмотрим все остальные блоки. В качестве еще одного нюанса (он тоже описан в документации) необходимо отметить, что если блоки задействованы в обработке цикла, то им присваивается индекс, где номер до разделительной точки равен номеру кубика "Цикл", что говорит о последовательности выполнения собранной конструкции и о фактически задействованных в ней кубиках помимо самого кубика "Цикл".
На сим заканчиваем данный пост. До новых встреч, в которых дальше продолжим разбираться с малоописанным функционалом и возможностями его практического применения для решения задач по расчету адаптивных индикаторов, выставления "сетки" заявок и т.д. :-)