Совместное использование разномасштабных графиков

Опубликовано в AmiBroker

Не знаю, как охарактеризовать эту статью. Скорее всего как описание трюка (методики?) при написании робота в AmiBroker/AmiSharp.

Заказчику потребовался робот, использующий одновременно несколько индикаторов. Все эти индикаторы имеют очень большой разброс по масштабу величин. Например, сама цена измеряется в сотнях тысяч, стохастик - от 0 до 100, MACD и Чайкин, объёмы - по своим, заранее неизвестным пределам. Между тем, алгоритм для правильной работы требует расчета всех индикаторов.

Хотя Заказчик никак не артикулировал необходимость визуализации всех индикаторов, необходимость её очевидна. Как можно догадаться, логика работы торгового алгоритма довольно сложна, в некоторые моменты для меня вообще нетривиальна. Исходя из последнего, посмотреть на индикаторы будет полезно не только Заказчику при использовании и мне при разработке. Значит, все индикаторы рисуем. Но как быть с масштабированием? Как поместить все эти индикаторы на один график и после этого ещё умудриться на нем что-либо разобрать?

Очевидно, что график цены должен присутствовать постоянно - на нем рисуются торговые сигналы, метки реальных сделок, уровни и тому подобная информация. Значит, этот график нужно принять основным.

Следующий - стохастик. Как с ним поступить? Вывести с собственной шкалой? Да, можно, пределы изменения индикатора заранее известны. Правда уровни индикатора стохастик будут постоянно путаться с уровнями на основном графике цены. Неудобно.  Потом MACD - с этим вообще головная боль - придется рассчитывать мин и макс в видимой части графика и масштабировать соответственно на лету. Потом добавляем индикатор Чайкина и на экране полная каша, как ни масштабируй и не дели пространство на зоны. Умри мои глаза.

Законный вопрос. А что он мучается? Почему не поместить индикаторы в отдельные скрипты и не расположить их в отдельных панелях на том же чарте? Вопрос резонный, попробую. Получается следующая схема: есть основной скрипт, рисующий основной график цены, рассчитывающий сигналы и выполняющий функции робота (выставление заявок, контроль позиций, себестоимостей, рисков). И несколько дополнительных скриптов, каждый из которых занят расчетом и визуализацией одного единственного вспомогательного индикатора. Опять же получается очень приятное разделение робота на составные части, с которыми гораздо легче работать, чем с одним большим исходным текстом. Да и идеологически подход хорош - в каждой части своя законченная логика. Ощущение, что направление верное. Но как получать результаты расчета этих индикаторов в главном скрипте?

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

Здесь есть один момент. Если в Амиброкере будут запущены, к примеру, две копии робота, работающие с разными инструментами и/или разными таймфреймами, то у нас будут 2 главных скрипта и по два на каждый дополнительный индикатор. В этом случае необходимо различать результаты, создаваемые каждым из аналогичных индикаторов. Этого можно добиться, сделав идентификаторы StaticVar функциях уникальными, например, используя разные префиксы для каждого робота.

Казалось бы все проблемы разрешены. Тем не менее, законы Мерфи никуда не делись. "Если все хорошо - значит, что вы всего дишь чего-то не заметили". Бектест - вот где нас поджидает засада. Помещаем в него главный скрипт и...  Беда... Скрипты дополнительных индикаторов-то недоступны! Что же теперь, дублировать содержимое каждого дополнительного индикатора в главном модуле? Шило-мочало. Отказать. Что получается? В режиме индикатора все скрипты хотелось бы разложить по отдельным файлам, а во всех остальных режимах вроде как должны лежать в одном.

Проблема, однако. Что делать? "Если на пятый день мучений у Вас по-прежнему ничего не получается - прочтите наконец документацию!".

Читаю. В языке AFL есть примитивный препроцессор с директивой #include. Идея: если текущий режим работы скрипта индикатор - то все скрипты комплекта запускаем отдельно и обмениваемся данными через StaticVar. Если это любой иной режим (например бэктест или оптимизатор), то через #include подтягиваем все дополнительные скрипты в тело основного скрипта. Из двух режимов использования директивы #include выбираем тот, который с угловыми скобками. Это позволит избежать путаницы с полными путями и рабочим каталогом: если имя файла в в директиве #include помещено в угловых скобках (а не в двойных кавычках), то файл будет включаться из стандарной папки INCLUDE амиброкера. Туда и поместим все наши скрипты дополнительных индикаторов. Основной индикатор поместим, скажем в папку SYSTEMS.

Фрагмент главного скрипта, где идет подключение дополнительных скриптов, может выглядеть так:

if (Status("action") != actionIndicator)
{
            #include <msc-MACD.afl>
            #include <msc-stoch.afl>
            #include <msc-Chaikin.afl>
}

Проверяем в режиме индикатора - все ок. В режимах бектеста, оптимизатора и прочих - тоже. Ура-ура.

Таким образом, убиты сразу несколько мух/зайцев:

  • Дополнительные индикаторы разложены по отдельным панелям
  • Код робота разделен на логически завершенные блоки
  • Избежали дублирования кода при использовании скрипта в различных режимах
  • Обошли необходимость указания абсолютных путей при включении файлов
  • Огромный список настроек робота сам собой разделился на части, каждая из которых вызывается из соответствующей панели (для индикатора)
В сухом остатке. Прошло больше месяца с момента вручения Заказчику этого робота. Раз претензий нет, значит и подводных камней тоже - подход имеет право на существование.

Недостаточно прав для комментирования