Секреты TSLab | Торговые роботы | События
12 Мар

Кэш скриптов для чайника. Часть 2.

В статье “Кэш скриптов для чайника. Часть 1” мы начали разбирать приемы работы с кэшэм. Во второй части изучим приемы кэширования индикаторов, что позволит в разы ускорить оптимизацию.

Кэш скриптов – катализатор оптимизации.

Чтобы показать как ваши торговые роботы могут использовать кэширование, рассмотрим практический пример примитивной стратегии суть которой заключается в следующем:  входим в лонг, когда быстрая SMA пересекает медленную SMA снизу вверх, выходим из лонга когда происходит обратное пересечение. Накидаем простой скрипт:

using TSLab.Script;
using TSLab.Script.Handlers;
using TSLab.Script.Helpers;
using TSLab.Script.Optimization;

namespace test
{
    public class SmaSimpleStrategy : IExternalScript
    {
        public OptimProperty FastSmaPeriod = new OptimProperty(10, 10, 300, 10);
        public OptimProperty SlowSmaPeriod = new OptimProperty(20, 10, 300, 10);

        public void Execute(IContext ctx, ISecurity sec)
        {
            var fastSma = Series.SMA(sec.ClosePrices,FastSmaPeriod);
            var slowSma = Series.SMA(sec.ClosePrices, SlowSmaPeriod);

            for (int i = 0; i < ctx.BarsCount; i++)
            {
                var le = sec.Positions.GetLastActiveForSignal("LE", i);
                if (le == null)
                {
                    if (fastSma[i] > slowSma[i] && fastSma[i-1] < slowSma[i-1])
                        sec.Positions.BuyAtMarket(i+1, 1, "LE");
                }
                else
                {
                    if (fastSma[i] < slowSma[i] && fastSma[i - 1] >= slowSma[i - 1])
                        le.CloseAtMarket(i + 1, "LX");
                }
            }

            if (ctx.IsOptimization)
                return;

            var color = new Color(System.Drawing.Color.Black.ToArgb());
            var pane = ctx.CreatePane(sec.Symbol, 100, false);
            pane.AddList(sec.Symbol, sec, CandleStyles.BAR_CANDLE, color, PaneSides.RIGHT);

            color = new Color(System.Drawing.Color.Green.ToArgb());
            pane.AddList("FastSma", fastSma, ListStyles.LINE, color, LineStyles.SOLID, PaneSides.RIGHT);

            color = new Color(System.Drawing.Color.Blue.ToArgb());
            pane.AddList("SlowSma", slowSma, ListStyles.LINE, color, LineStyles.SOLID, PaneSides.RIGHT);
        }
    }
}

Результат работы данного кода видим на скриншоте ниже:

кэш скриптов для чайника. часть 2. рисунок 1.

Рисунок 1. Результат работы стратегии.

Если мы теперь заведем данный скрипт на оптимизацию, то получим следующие результаты по времени:  56 секунд.

Вводим в код стратегии небольшие усовершенствования, которые задействуют кэширование:

            var fastSma = ctx.GetData("SMA", new[] {FastSmaPeriod.ToString()},
                                      () => Series.SMA(sec.ClosePrices, FastSmaPeriod));

            var slowSma = ctx.GetData("SMA", new[] { SlowSmaPeriod.ToString() },
                                      () => Series.SMA(sec.ClosePrices, SlowSmaPeriod));

  После этих изменений время оптимизации составляет 30 секунд. Ускорение почти в 2 раза, и это далеко не предел. В зависимости от сложности расчета кэшируемых данных это ускорение может быть очень значительным. Очень крутое ускорение мы можем наблюдать когда используем адаптивные индикаторы.

Разберем в деталях те дополнения в коде, которые приводят к такому замечательному ускорению. Упоминаемый метод “GetData()” вызывается из объекта передаваемого в скрипт через переменную “ctx”. Сигнатура метода находится в предыдущей статье, поэтому здесь мы сосредоточимся на разборе метода в подробностях.

Когда вы пытаетесь кэшировать индикаторы, кэш скриптов для вас будет представлять собой таблицу КЛЮЧ-ЗНАЧЕНИЕ. Где КЛЮЧ – любая строка, а ЗНАЧЕНИЕ – список элементов типа double. Когда значения индикатора рассчитаны, они будут помещены в кэш скриптов под заданным текстовым ключом, например “SMA5″.

Первый аргумент метода представляет собой префикс ключа. Данный префикс надо выбирать с умом, чтобы максимально задействовать мощь кэширования. Чуть ниже я приведу примеры правильного и неправильного задания префикса.

Второй аргумент представляет собой массив строковых значений из которых будет формироваться оставшаяся часть ключа. Например префикс у нас “SMA”, а в массиве указан один элемент FastSmaPeriod который по умолчанию равен 10. Соединяя все воедино, получаем ключ “SMA10″.

Третий аргумент должен быть делегатом или лямбда выражением, что проще закодировать. Этот аргумент является специальным методом, который будет вызван в случае отсутствия в кэшэ данных для ключа.

Если разбирать на конкретных примерах, то алгоритм выходит следующий. Сначала, у нас кэш скриптов не содержит ничего. Представим, что выполняется строка расчета fastSma  под номером 15. Метод GetData() формирует ключ и получает “SMA10″. Далее он запрашивает в кэшэ данные для этого ключа. Естественно, никаких данных для ключа нет. Тогда вызывается метод, указанный третьим аргументом, который должен вернуть список чисел double. Этот список записывается в кэш скриптов под ключом “SMA10″ и после этого он возвращается из метода GetData(). Теперь у нас рассчитывается строка slowSma. Ключ формируется такой же “SMA10″, ведь мы расчет скользящей делаем так же по ценам закрытия. Метод “GetData()” запрашивает данные в кэшэ и находит там их. Расчет заново запускаться не будет, и данные сразу будут возвращены из метода. За счет такого нехитрого способа мы избегаем повторных расчетов скользящей для повторяющихся периодов.

ВАЖНО: если мы работает в режиме тестирования и исходные данные по инструменту не изменяются, то метод GetData() будет возвращать из кэша результаты прошлых расчетов сохраненных в нем. Если мы работаем в реалтайме, то есть инструмент изменяется, тогда метод GetData() после каждого изменения инструмента будет обнуляться и расчет будет запущен заново. Если подытожить, то кэш метода GetData() сбрасывается на каждом  пересчете скрипта инициированном изменениями по инструменту.

Неправильное использование кэша скриптов.

Теперь пример неправильного задания префикса для ключа:

            var fastSma = ctx.GetData("FastSMA", new[] {FastSmaPeriod.ToString()},
                                      () => Series.SMA(sec.ClosePrices, FastSmaPeriod));

            var slowSma = ctx.GetData("SlowSMA", new[] { SlowSmaPeriod.ToString() },
                                      () => Series.SMA(sec.ClosePrices, SlowSmaPeriod));

В чем тут ошибка? Мы задали разные префиксы для быстрой и медленной скользящей, в итоге теряем часть ускорения в расчетах. Ведь в обоих случаях у нас простая скользящая средняя по ценам закрытия, и расчет может отличаться только в том случае, когда период отличается, ведь цены закрытия у нас одинаковые все время. Не забываем, что во время тестирования и оптимизации в TSLab исторические данные неизменны. Следуя этому можно заключить, что ключ должен содержать все изменяющиеся уникальные элементы в расчете, а так же параметры индикаторов от которых зависит конкретный расчет, но не должен включать посторонние элементы. Например:

            var fastSma = ctx.GetData("SMA", new[] {FastSmaPeriod.ToString(), SlowSmaPeriod.ToString()},
                                      () => Series.SMA(sec.ClosePrices, FastSmaPeriod));

            var slowSma = ctx.GetData("SMA", new[] { SlowSmaPeriod.ToString(), FastSmaPeriod.ToString() },
                                      () => Series.SMA(sec.ClosePrices, SlowSmaPeriod));

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

Ну и последний вариант ошибочного префикса:

            var fastSma = ctx.GetData("SMA", new[] {FastSmaPeriod.ToString()},
                                      () => Series.SMA(sec.ClosePrices, FastSmaPeriod));

            var slowSma = ctx.GetData("SMA", new[] { SlowSmaPeriod.ToString() },
                                      () => Series.SMA(sec.OpenPrices, SlowSmaPeriod));

Тут наблюдается очень опасная ошибка. Расчет скользящей быстрой у нас по ценам закрытия, а скользящей медленной по ценам открытия, но ключ для кэша у нас формируется одинаковый. Префикс у нас одинаковый и при одинаковых периодах мы будем получать из кэша кривые данные. Обязательно тут нужно ставить разные префиксы.

Примеры нестандартного кэширования.

Допустим, нам необходимо строить канал дончиана по скользящей, а не просто по ценам закрытия. В таком случае организация кэширования будет такой:

            var slowSma = ctx.GetData("SMA", new[] { SlowSmaPeriod.ToString() },
                                      () => Series.SMA(sec.ClosePrices, SlowSmaPeriod));

            var high = ctx.GetData("Highest", new[] { HighPeriod.ToString(), SlowSmaPeriod.ToString() },
                                      () => Series.Highest(slowSma, HighPeriod));

Так как граница канала зависит не только от периода, но и от исходных данных по которым он строится, то нужно это учитывать. Исходные данные зависят от периода медленной скользящей. Значит граница канала зависит от двух параметров, период канала и период медленной скользящей, что мы, собственно, и вписываем в метод GetData(). Естественно, что префикс мы задаем отличный от SMA :).

Наконец, приведу пример посложнее. Внутри лямбды мы создаем объект индикатора и рассчитываем его. Результат расчета будет закэширован.

            var adx = ctx.GetData("adx", new[] { AdxPeriod.ToString()},
                                  () =>
                                      {
                                          var adxHnd = new TSLab.Script.Handlers.ADXFull()
                                              {
                                                  Context = ctx,
                                                  Period = AdxPeriod
                                              };
                                          return adxHnd.Execute(sec);
                                      });

Надеюсь теперь ваши скрипты будут работать значительно быстрее, так как вы овладели кэшированием на полную катушку. В следующей части разберем как сохранять данные между пересчетами скрипта и получать от этого пользу.

PS: Не забывайте про самую главную опасность кэширования! Если вы ключи зададите неправильно, вы рискуете получить совершенно неадекватную работу  скрипта. Например, вместо расчета скользящей, будете получать данные от расчета ATR.

Продолжение следует в третьей части


comments powered by HyperComments

Жора
2017-01-01 06:52:28
Казино Вулкан раздают деньги сегодня http://cenforce100.ru/casino-vulkan.php
6
Ноя
2017

Доверительное управление. Результаты в октябре 2017 года.

Доверительное управление. Результаты в октябре 2017 года. В октябре продолжался боковик на всех торгуемых активах. Российский… »

7
Окт
2017

Доверительное управление. Результаты в сентябре 2017 года.

Доверительное управление. Результаты в сентябре 2017 года. В сентябре на основных торгуемых активах (Ri и… »

8
Сен
2017

Доверительное управление. Результаты в августе 2017 года.

Доверительное управление. Результаты в августе 2017 года. Стратегия “Опционы” принесла в августе прибыль в размере… »

6
Авг
2017

Доверительное управление. Результаты в июле 2017 года.

Доверительное управление. Результаты в июле 2017 года. В июле индекс РТС вновь колебался в достаточно узком… »

14
Июл
2017

Доверительное управление. Результаты в июне 2017 года.

Доверительное управление. Результаты в июне 2017 года. Июнь индекс РТС вновь провел преимущественно в боковых движениях, а… »