СУПЕР пример создания простого робота на языке Qpile.

На мой взгляд, это лучший пример для изучения  программирования для Квика!!!


 


На чем пишем?


Написать автомат для торговли можно практически на любом современном языке программирования, самое главное – установить обмен данными между терминалом (или шлюзом биржи) и автоматизированной торговой системой. А это требует достаточно серьезных навыков программирования. Самый доступный путь – написание робота на языке Qpile.


Плюс этого языка состоит в том, что он прост и интегрирован непосредственно в терминал Quik [1], что повышает надежность связки «Терминал-Робот». Из минусов можно выделить отсутствие интерфейса взаимодействия с пользователем (то есть программу можно запустить и остановить, но управлять ею в процессе работы нельзя). Также проблематично на Qpile обрабатывать большие массивы данных, что накладывает ограничение на создание механических систем для работы с большим количеством входных параметров. Но для реализации простых стратегий функционала этого языка вполне достаточно.


С чего начать?


Робот – это программный код, который можно набрать в любом текстовом редакторе, даже в «Блокноте» стандартного пакета Windows. Однако удобнее его писать в специализированных редакторах. В них автоматически нумеруются строки и подсвечиваются операторы, что улучшает восприятие кода и упрощает его отладку. Наиболее распространены редакторы «Notepad + +» и «SciTE», они бесплатны, и для них можно найти модули для корректной подсветки синтаксиса Qpile. Есть также специализированный редактор «Qpile Master 1.2», который в бесплатном варианте имеет ограниченный функционал.


Стратегия и инструмент


Любой робот работает по заранее заданному алгоритму, который является логическим воплощением торговой стратегии. Cтратегия является самой важной частью любого автомата (хотя по объему может занимать небольшую часть кода). Ее выбор – основополагающий этап для построения любой торговой системы, именно она делает одну систему прибыльной, а другую убыточной при прочих равных условиях. Вопросу выбора стратегии посвящено немало книг [2,3]. Целью данного цикла статей является процесс написания робота, который самостоятельно принимает решения о покупке/продаже, выставляет заявки и проверяет их исполнение (но не гарантируется, что он обязательно окажется прибыльным). Какой алгоритм выбрать для торговой системы, каждый трейдер решает сугубо индивидуально. Мы же рассмотрим одну из самых простых стратегий – торговлю в канале. Ее алгоритм прост: если цена ниже определенного уровня, покупаем, а если выше – продаем. Если котировки выходят за пределы канала в невыгодном для нас направлении, фиксируем убытки.


В качестве рабочего инструмента используем фьючерс на обыкновенные акции Сбербанка. На момент написания статьи ближайшими контрактами были SRU1 (исполнение 14 сентября 2011 года) и SRZ1 (исполнение 14 декабря). Этот инструмент хорош тем, что характеризуется высокой ликвидностью и небольшим размером гарантийного обеспечения (1700 рублей на момент написания статьи). То есть, если мы будем торговать одним контрактом и закрывать позицию в случае убытка, даже в худшем случае это не приведет к существенным финансовым потерям. Но не следует забывать, что фьючерс имеет ограниченный срок жизни и вхождение в позицию незадолго до исполнения увеличивает риск возможных потерь. Для покупки и продажи выберем уровни 8500 и 9100 рублей (см. рис. 1).


Рис. 1 Динамика сентябрьского фьючерса на обыкновенные акции ОАО «Сбербанк России»


СУПЕР пример создания простого робота на языке Qpile.


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


Реализовать стратегию торговли в канале можно двумя способами. Первый подразумевает отслеживание уровня цены и выставление заявки при достижении цели. Также можно сразу размещать ордера на уровнях, независимо от текущей цены, и затем следить за исполнением. Первый способ применим только для инструментов, цены которых изменяются не слишком быстро, в противном случае, при резких движениях можно просто не успеть. Второй метод подходит для всех активов, однако при этом надо все время следить за исполнением заявок, при необходимости их переставлять или же во- все снимать (например, при динамически изменяющихся уровнях покупки/продажи). Первый подход более прост в реализации, поэтому пока остановимся на нем. А бороться с быстрым изменением цены и неисполнением заявок будем заведомо более выгодными для рынка ценами, то есть в них будет изначально закладываться проскальзывание.


Шаг 1


Итак, стратегия и инструмент определены. Открываем редактор и начинаем писать код. Любая программа на Qpile должна содержать одну таблицу для вывода данных, как минимум, с одним столбцом. Для начала напишем код, состоящий всего из одного оператора. Он будет выводить в таблицу текущее время сервера (см. код «Время сервера»).


После того, как код написан, сохраняем файл с расширением *.qpl, затем в терминале Quik открываем пункт меню «Таблицы->Портфели->Задать портфель» (программы в Quik именуются портфелями) или нажимаем клавиши Ctrl+F10. Выбираем наш файл, кликаем на кнопке «Открыть», «Загрузить локально», а затем «Выход» (см. рис. 2).


Рис. 2 Пункты меню для загрузки и запуска программ Qpile


СУПЕР пример создания простого робота на языке Qpile.


Наша программа загружена! Все портфели в Quik работают в циклическом режиме с заданным периодом, который по умолчанию равен 10 секундам. Чтобы уменьшить его, необходимо сделать соответствующие настройки: открываем пункт меню «Таблицы->Портфели->Доступные портфели» (или Ctrl+F11), выбираем наш портфель и ставим период расчета 1 сек (это минимальное значение), после чего сохраняем изменения. Эту операцию не надо проводить каждый раз, установленное значение сохраняется для каждого загруженного портфеля отдельно. Чтобы запустить программу, в меню находим «Таблицы->Портфели->Просмотр портфеля» (или Ctrl+F12), выбираем наш портфель, а в «Доступных параметрах» — «Добавить все». После клика на кнопке «Да» программа начинает работать, и появляется наша таблица. Если в коде есть явные ошибки, будет выдано сообщение с их описанием и номером строки кода. Если ошибки не явные (например, логические), то сообщение может и не появиться, но данных в таблице не будет. В этом случае программу можно запустить в режиме отладки. Кликаем по нашей таблице правой кнопкой мыши и выбираем «Начать расчет в режиме отладки». По шагам можно отследить ход выполнения программы и значения переменных. Если код достаточно длинный, для отладки можно использовать функцию BREAKPOINT(), которая останавливает выполнение программы в заданной точке и запускает режим отладки. Если все сделано правильно, в нашем случае появится таблица с одним столбцом «TIME» и текущим временем сервера, к которому подключен терминал (см. рис. 3). Чтобы остановить программу, достаточно закрыть окно с таблицей.


Рис. 3 Программа, выводящая текущее время сервера


СУПЕР пример создания простого робота на языке Qpile.


Шаг 2


Теперь, когда первая программа заработала, начинаем ее усовершенствование: будем получать текущие цены спроса/предложения по выбранному инструменту, значения наших уровней, название инструмента и время до исполнения в днях. Все эти данные так- же выведем в таблицу (см. код «Инструмент»). Загрузим программу в Quik и запустим ее. Если все реализовано без ошибок, мы получим следующую таблицу (см. рис. 4).


Рис. 4 Вывод параметров инструмента в таблицу


СУПЕР пример создания простого робота на языке Qpile.


СУПЕР пример создания простого робота на языке Qpile.


Скачать код


У нас есть все необходимые данные, поэтому остается лишь сравнивать текущие цены с нашими уровнями. Если цена спроса выше верхнего уровня, это сигнал к про- даже, если предложение ниже нашего нижнего уровня, необходимо осуществить покупку. Оформим эту логику в коде (см. код «Сравнение», приведено только тело основной программы).


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


' ЕслиценаспросавышеуровняHI, надопродавать


IF PriceBid >= HiLevel SERVER_TIME =«SELL»


END IF


' Если цена спроса ниже уровня LOW, надо покупать


IF PriceOffer <= LowLevel SERVER_TIME = «BUY»


END IF


В этом случае сигналы на покупку/продажу будут выводиться, но в столбец «TIME» таблицы.


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


Не рекомендуется сразу пытаться торговать с помощью робота на реальном счете (особенно в начале пути приобретения опыта написания программ), поскольку даже при потенциально прибыльной стратегии возможны ошибки в коде, которые, в свою очередь, могут привести к серьезным потерям (допустим, выставляется заявка с неправильной ценой и/ или объемом) вплоть до обнуления счета. Целесообразно для отладки кода использовать демо-счет.


СУПЕР пример создания простого робота на языке Qpile.


Скачать код Инструмент


СУПЕР пример создания простого робота на языке Qpile.


Скачать код Сравнение


 


Условно можно выделить два основных фактора, которые делают механическую торговую систему (МТС) убыточной: плохая стратегия, или нерациональное управление капиталом, и сама инфраструктура МТС, точнее, механизмы ее реализации. Сегодня наша задача свести возможные потери, зависящие исключительно от способов воплощения МТС, к минимуму.


Итак, наша программа (см. F&O №8–9, 2011, код можно скачать с сайта журнала fomag.ru/articles/write_the_robot_moves.php) уже выдает советы по покупке/продаже активов. Осталось снабдить ее функциями выставления заявок, и мы получим полноценного робота. Но торопиться не стоит. Если портфель загружен в Quik, он начинает работать с запуском самого терминала, а это не совсем корректно, поскольку программа может пытаться выставлять массу ошибочных заявок еще до того, как загрузятся все данные. Важно, чтобы робот начинал работу лишь тогда, когда Quik успел получить необходимую биржевую информацию. Кроме того, не будем забывать, что Quik обрабатывает все портфели последовательно, а не параллельно. То есть, если в одной из программ возникнет ошибка, она может остановить и все остальные портфели, что, в зависимости от алгоритма, может привести к прямым или косвенным убыткам. Это подчеркивает необходимость сохранения работоспособности робота в любой ситуации. Проанализировав различные ситуации, возникающие в процессе торговли, сформулируем некоторые правила, которых должен придерживаться наш робот:


1. Начало работы по заданному алгоритму необходимо осуществлять только при наличии соединения с сервером;


2. Расчет основной стратегии производится только при наличии актуальных данных;


3. Стратегия работает только в то время, когда идут торги по текущему инструменту;


4. Выставление заявок на покупку/продажу производится только при наличии необходимой суммы на счете для ее обеспечения;


5. Нельзя открывать новые позиции, если общая сумма на депозите ниже заданного уровня;


6. Робот постоянно находится в рабочем состоянии.


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


Торопиться не надо


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


между циклами). Одна будет определять количество пустых циклов, а другая – отсчитывать количество прошедших циклов:


NEW_GLOBAL («Delay»,10)


' Количество пустых


циклов


NEW_GLOBAL


(«TIMER»,0)


Далее в теле программы создадим условие:


IF TIMER <= Delay


TIMER = TIMER + 1


RETURN


END IF


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


Функция контроля ситуаций


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


Функции в Qpile могут иметь исходные аргументы и возвращать результат. Наша функция начальных аргументов иметь не будет. Результатом ее выполнения станет соответствующее название ошибки (ситуации), или, например, она будет возвращать «ОК», если все условия нормальной работы выполняются.


При написании функций желательно использовать имена переменных, которые не пересекаются с переменными в основной программе. Одинаковые переменные в теле основной программы и функции могут привести к их пересечению, а это может породить некорректные расчеты или «зацикливание». Последнее наиболее вероятно при вызове функции из цикла «FOR …FROM …TO….».


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


ERR = Control()


IF ERR<>«OK»


DELETE_ALL_ITEMS()


output=CREATE_MAP()


output=SET_


VALUE(output,«TIME»,


ERR)


ADD_ITEM(1,output)


RETURN


END IF


Сама функция для удобства восприятия будет располагаться в конце программы. Если функций много, их можно подключить через отдельный файл (см. Руководство пользователя Quik. Раздел 8).


Каждая функция оформляется следующим образом:


FUNC Control()


[Тело функции]


END FUNC


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


Отсутствие соединения с сервером. Для контроля коннекта существуетспециальный оператор IS_CONNECTED(), который возвращает «1», если соединение установлено:


 


IF IS_CONNECTED()<>1


Result = «ErrorConnection»


' Ошибка соединения с


сервером


TIMER=0


Return


ELSE


Result = «OK»


END IF


В случае потери соединения переменной TIMER присваиваем значение «0». Таким образом, после восстановления связи мы вновь включим задержку, чтобы терминал успел обновить данные.


Время торговой сессии. Основная торговая сессияна FORTS начинается в10:00 и заканчивается в23:50 мск, при этом имеетсядва перерыва на клиринг: дневной длится с 14:00 до14:03, а вечерний – с 18:45до 19:00 мск (до 19:10 в дниэкспирации опционов).Следовательно, программа должна работать поалгоритму, только когдаидут реальные торги, а востальное время она находится в режиме ожидания.Фрагмент программногокода, который проверяеттекущее время, представлен в справочной информации к статье.


SP 10-1.jpg


Главное – вовремя остановиться. Умениеостановиться вовремя ине торговать, когда уже до-пущено много ошибок, может сберечь значительнуюсумму любому трейдеру.Наделим этой способностью и нашего робота.Если размер счета станетменьше заранее заданногоуровня (в качестве примера зададим минимальныйуровень в 50 тыс. рублей), программа не будет открывать новые позиции.Тем самым наш счет будетзастрахован от полногоопустошения в случаебольшого количества убыточных сделок. Фрагменткода, который приводит костановке открытия новыхпозиций при достиженииопределенной величиныпросадки счета, представлен в справочной информации к статье.


SP 10-2.jpg


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


Unit = 1 ‘ Количество


контрактов для одной


сделки


WARRANTY =0


+ GET_PARAM


(ClassCode,SecCode,


«BGONP») ' Размер


гарантийного обеспечения


IF AvailableMoney <


(2*WARRANTY*Unit) AND


nContract = 0


Result =«ErrorWarranty» '


Не хватает средств для ГО


Return


ELSE


Result = «OK»


END IF


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


ExpDays =0 + GET_


VALUE(GET_PARAM_EX


(ClassCode,SecCode,


«DAYS_TO_MAT_DATE»),


«PARAM_IMAGE»)


IF ExpDays <= 2 AND


nContract = 0


Result =«ErrorEXP» ' До


исполнения менее 2-х дней


Return


ELSE


Result = «OK»


END IF


Все ходы записаны


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


File = «C:\


Qpile\”&Seccode&”_Robot.


log”


В приведенном выше примере файл будет размещен на диске «C:» в папке «Qpile», которую предварительно требуется создать. Или можно указать любую другую директорию. Имя файла будет начинаться с обозначения инструмента, в нашем случае оно будет иметь вид «SRZ1_Robot.log» (SRZ1 – код декабрьского фьючерса).


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


NEW_GLOBAL


(»FirstTime",1)


IF FirstTime = 1


WRITELN (File, SERVER_


TIME&" START")


FirstTime = 0


END IF


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


Итог


Итак, мы научили нашего робота обходить нестандартные ситуации. Напрямую это не увеличивает прибыльность системы, но позволяет значительно повысить устойчивость работы и сократить количество ошибочных сигналов, что снижает возможные «нерыночные» риски. Справедливости ради стоит отметить, что жизнь гораздо богаче на события, чем мы иногда можем предположить. В этой статье мы рассмотрели наиболее распространенные ситуации, но могут встречаться и другие, например, достижение предельной цены фьючерса, технические сбои на бирже, перегрузки серверов и так далее. Помните, каким бы «умным» ни был наш робот, всегда найдется такое стечение обстоятельств, к которому он окажется не готов. Конечно, автомат может значительно облегчить работу трейдера, но, пожалуй, заменить его полностью он пока не способен.


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


 


Итак, наш робот уже многое может (смотри F&О №8-9,10 за 2011 год), но для воплощения торговой стратегии в жизнь ему не хватает умения работы с заявками. Определим порядок действий, которые должна воспроизводить механическая торговая система при появлении сигналов на покупку/продажу:


1. Выставляем заявку на покупку/продажу в зависимости от сигнала;


2. Ждем исполнения торгового приказа;


3. Если заявка исполнилась, сразу выставляем новый «стоп-ордер» и снимаем «старый» (если таковой имеется);


4. Если заявка не исполнилась за отведенный промежуток времени, снимаем заявку и ждем нового сигнала.


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


1. Функция выставления заявки;


2. Функция выставления «стоп-ордера»;


3. Функция покупки;


4. Функция продажи;


5. Функция снятия заявки;


6. Функция снятия «стоп-ордера»;


7. Функция контроля исполнения заявки;


8. Функция Stop-Loss.


Функции выставления заявок


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


Аргументами для функции отправки обычных заявок являются: направление (покупка или продажа, “B”/”S”), цена, тип (рыночная (“M”) или лимитированная (“L”)), код инструмента и класса. Для функции стоп-заявок необходим дополнительный параметр – стоп-цена. Кроме того, она будет возвращать результат – номер стоп-заявки. Когда стоп сработает, он отправит ордер на покупку или продажу для закрытия текущей позиции по рыночной цене. ID транзакции – уникальный идентификационный номер – будем формировать из текущего времени в каждом цикле программы.


' Формируем


идентификатор TransID


SERVER_


TIME=GET_INFO_


PARAM(«SERVERTIME»)


TransID =0+(SUBSTR


(SERVER_TIME,0,2)&


SUBSTR(SERVER_


TIME,3,2)& SUBSTR


(SERVER_TIME,6,7))


Обмен данными между терминалом и программами на Qpile чаще всего происходит через ассоциативные массивы (MAP), в том числе и отправка заявок осуществляется посредством передачи данных в массиве. Поэтому функция отправки торговых приказов, по сути, просто заполняет необходимые элементы массива и передает последний в терминал. Когда биржа получает ордер, она должна идентифицировать клиента, который его выставляет. Для этого указываются уникальный код клиента и его счет (найти их можно в таблице «Ограничения по клиентским счетам»). После успешного принятия ордера торговой площадкой функция устанавливает флаг наличия активной заявки FlagOrder = 1 и запоминает ее номер в глобальной переменной OrderNumber.


Account=«00000000000»


ClientCode =«00000000000»


NEW_GLOBAL


(«FlagOrder»,0) '


Глобальная переменная


– флаг наличия активной


заявки


NEW_GLOBAL


(«OrderNumber»,0) '


Глобальная переменная –


номер активной заявки


NEW_GLOBAL


(“TransID",1) '


Идентификатор


транзакции


Order_TIMER=«30» '


Длительность периода


ожидания ответа


торговой площадки


Коды функций выставления заявок на языке Qpile приведены в справке.


SP 11.jpg


Покупка и продажа


Функции покупки и продажи по своей логике идентичны, но имеют противоположное значение, поэтому также рассмотрим их вместе. В качестве аргументов будем использовать код инструмента, его класс и количество контрактов (Qty), которое необходимо купить или продать. Эти функции сами рассчитывают цену заявки исходя из лучшей цены спроса/предложения и максимального проскальзывания (Slip). Последнее указывается в единицах минимального шага цены инструмента. Коды функций покупки и продажи на языке Qpile приведены в справке.


SP 11-1.jpg


Функции снятия и контроля исполнения заявок


Снятие заявок такж происходит через ассоциативный массив. Процессы снятия заявок и стопов практически идентичны, аргументами для них являются номер приказа, код инструмента и код класса. Программный код функций снятия заявок по номеру можно найти в справке.


Когда ордер принят торговой системой, остается ждать его исполнения. Для этого достаточно остановить работу программы до наступления этого момента. Но на исполнение приказа может потребоваться некоторое время, и, если ждать исполнения внутри одного цикла программы, оно может превысить период одного цикла (особенно, если последний составляет несколько секунд). Чтобы избежать этого, исполнение ордера будем проверять в последующих циклах. Факт совершения сделки определим по остатку в текущей активной заявке: если он равен «0», заявка полностью удовлетворена. Если приказ не исполнен за отведенное количество циклов (Cycles), то снимаем его. В приведенном примере количество циклов ожидания равно 20. То есть при периоде расчета 1 секунда максимальное время ожидания составит 20 секунд.


NEW_GLOBAL («Count»,0)


' Счетчик циклов


NEW_GLOBAL


(«FlagStopOrder»,0) ' Флаг


стоп-ордера


NEW_GLOBAL


(«PriceStopOrder»,0) '


Цена последней заявки –


базовая цена для расчета


стоп-ордера


Cycles =20 ' Количество


циклов ожидания


исполнения заявки


Код функций контроля исполнения заявок приведен в справке.


SP 11-2.jpg


Функция Stop-Loss


Данная функция будет выставлять новый стоп-ордер и снимать предыдущий. Аргументами для нее выступят код класса и код инструмента. В начале работы она определяет баланс по текущему инструменту: если он равен «0», просто снимаем все активные стоп-заявки (если они есть). Если баланс отличен от «0», то в зависимости от занимаемой позиции (короткая или длинная) выставляем новый стоп-ордер, а затем снимаем предыдущий активный стоп. Перед отправкой нового стоп-приказа функция рассчитывает цену стопа (StopPrice) исходя из цены последней заявки (PriceStopOrder) и величины стоп-лосса (wStopLoss). Переменная wStopLoss устанавливается в процентном выражении (в представленном варианте — 30%) от ширины основного канала Width (будет рассмотрена в следующей части).


Width = 0.8 ' Ширина


канала в %


wStopLoss = Width*0.3 '


Ширина стоп-лосса — 30%


от канала


Код функций выставления стоп-заявок представлен в справке.


SP 11-4.jpg


Мы описали необходимые функции для работы с заявками. Все активные действия будут автоматически фиксироваться в LOG-файле, с помощью которого можно контролировать работу программы. Обращения к функциям BUY и SELL будут происходить из основной программы при наличии сигналов на покупку/продажу. Обращения к ORDER_DONE и STOP_LOSS также осуществляются из тела программы при наличии соответствующих флагов: FlagOrder и FlagStopOrder.


SP 11-3.jpg


Новый взгляд


Если подробно рассмотреть алгоритм работы с заявками, представленный в начале данной статьи, но с учетом созданных функций, он будет выглядеть следующим образом:


1. При появлении сигнала на покупку/продажу идет обращение к функциям BUY/SELL;


2. BUY/SELL рассчитывают цену заявки и выставляют ее через функцию SEND_ORDER;


3. SEND_ORDER отправляет приказ в торговую систему и в случае его принятия биржей устанавливает флаг наличия активной заявки (FlagOrder=1), номер активной заявки (OrderNumber), после чего осуществляется переход на следующий цикл;


4. Если заявка активна (FlagOrder=1), вызывается функция ORDER_DONE, которая проверяет остаток (Rest) исполненных контрактов в текущей заявке;


5. Если остаток (Rest) больше 0, счетчик циклов (Count) увеличивается на единицу, и затем осуществляется переход на следующий цикл;


6. Если количество циклов ожидания (Count) превысило заданное максимальное число (Cycles), снимается заявка (функция KILL_ORDER), обнуляется флаг ее активности (FlagOrder=0), сбрасывается счетчик прошедших циклов (Count=0), после чего ожидается очередной сигнал торговой системы на покупку или продажу;


7. Если остаток (Rest) равен 0 (заявка исполнилась), то обнуляется флаг ее активности (FlagOrder=0), сбрасывается счетчик прошедших циклов (Count=0), устанавливается флаг стоп-ордера (FlagStopOrder=1), задается цена последней заявки (PriceStopOrder) и осуществляется переход на следующий цикл;


8. На следующем цикле при FlagStopOrder=1 идет обращение к функции STOP_LOSS, которая получает текущий баланс по инструменту;


9. При балансе равном нулю снимается стоп (функция KILL_STOP_ORDER), если имеется активный;


10. Если баланс отличен от нуля, выставляем защитный стоп-ордер (функция SEND_STOP_ORDER) и снимаем предыдущий (функция KILL_STOP_ORDER), если таковой имеется;


11. Снимаем флаг стоп-ордера (FlagStopOrder=0);


12. Ожидаем следующего сигнала.


Как видно из приведенного алгоритма, отправка заявок, контроль исполнения и выставление стоп-ордеров происходит в разных циклах программы. Такая реализация объясняется тем, что между этими событиями должно пройти некоторое время, чтобы данные в терминале успели обновиться. Например, если заявка удовлетворена, это должно найти отражение в количестве контрактов в таблице «Позиции по клиентским счетам». Если проверять баланс по инструменту сразу после сделки, в одном и том же цикле, данные не всегда успевают обновиться, и мы можем получить старое значение количества контрактов, что приведет к повторному (ложному) сигналу на покупку/продажу. Скорость обновления во многом зависит от качества связи между терминалом и сервером, а также от загрузки последнего. Следует обратить внимание на то, что качество канала определяется не только пропускной способностью (например, 10 Мб/с), но и зависит от количества потерянных пакетов и задержки их распространения. Кроме того, скорость выполнения программы может быть существенно меньше времени распространения пакетов данных от терминала до торговой площадки. Поэтому «плохой» канал может свести на нет преимущество быстродействующей системы. Таким образом, разнося процессы отправки заявок, проверки их исполнения и стоп-ордеров во времени (в разных циклах), мы увеличиваем устойчивость системы к задержкам при передаче данных.


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


Скачать коды к Шагу 3


Продолжение — Шаг 4.


SP 11-5.jpg


 


Как было отмечено в F&O №8–9 за 2011 год, наш робот реализует стратегию торговли в канале, но заранее заданный статичный канал остается актуальным для работы в течение очень короткого отрезка времени, особенно при высокой волатильности. Поэтому, как и было задумано, сделаем его динамическим, то есть уровни покупки/продажи будут рассчитываться в каждом цикле. При этом в качестве отправной точки используем значение технического индикатора «скользящая средняя»: исходя из этого уровня будем определять верхнюю и нижнюю границы канала.


 


График


Для этого будем обращаться к графику «скользящая средняя» (Moving Average) для получения текущих значений. Воспользуемся оператором GET_CANDLE_EX, который позволяет считывать значения технических индикаторов. При этом можно использовать текущее время, которое автоматически округляется до времени текущей свечи, что упрощает процесс обращения к графическим индикаторам. Для удобства и унификации частей торгового робота чтение значений графиков реализуем в виде отдельной функции, но для начала зададим Moving Average на нашем ценовом графике и присвоим ему идентификатор SRZ1_EMA (см. рис. 1). Скользящую среднюю построим на минутках со следующими характеристиками: 20 периодов, экспоненциальный метод расчета.


Рис. 1 Присваиваем идентификатор «скользящей средней»


Eroshkin1.png


В теле программы зададим переменную с именем нашей скользящей средней, которая (переменная) будет автоматически создаваться из кода используемого инструмента (в нашем случае SRZ1). Это существенно упрощает настройку робота для другого контракта: достаточно поменять значение переменной SecCode для другого фьючерса и создать необходимые графики с соответствующим именем, и наш робот готов торговать другим инструментом.


EMA=SecCode&"_EMA" '


Название графика


Код функции чтения текущих значений индикатора приведен в справке.


Аргументом для этой функции будет имя графика (EMA), а выходным параметром — массив типа «коллекция», который содержит: в ячейке под номером 0 время текущей свечи, а в ячейках под номерами 1,2,3 и так далее значения линий графиков. В рассматриваемом примере технический индикатор «скользящая средняя» представляет собой одну линию, поэтому используется только ячейка под номером 1. Но есть индикаторы, состоящие из нескольких линий (ADX, MACD), в этом случае значения последних будут последовательно записываться в ячейки 1,2,3 и так далее. В теле программы обращение к функции осуществляется следующим образом:


' Получаем значение


скользящей средней


GRAFF = GRAF (EMA)


EmaExp =0+ GET_


COLLECTION_


ITEM(GRAFF,1)


В результате обращения переменная EmaExp будет содержать текущее значение «скользящей средней». Если график отсутствует или его имя не совпадает с переменной EMA, EmaExp равно 0, то есть наши уровни также окажутся нулевыми, а это приведет к ошибочным сигналам, так как цена спроса всегда будет больше 0. Также функция может возвращать нулевые значения в моменты формирования новой свечи. Многие технические индикаторы рассчитываются на основе уровней свечи (OPEN, HIGH, LOW, CLOSE), в том числе и «скользящая средняя», и мы можем наблюдать ситуацию, когда время новой свечи уже стало отсчитываться (например, 15 часов 10 минут и 03 секунды), но при этом не было проведено ни одной сделки. Поэтому новая свеча не начала формироваться (свечи рассчитываются на основе сделок), и в этот промежуток времени – от начала отсчета новой свечи до первой сделки – мы получим нулевое значение «скользящей средней».


Кроме того, при чтении значений технических индикаторов необходимо обращать внимание на текущее время. Дело в том, что в связке «терминал-сервер-биржа» присутствует три времени: компьютера, на котором установлен QUIK, сервера, к которому подключен терминал, и самой биржи. В идеале время на них должно совпадать, однако на практике это не всегда так. Время компьютера может существенно отличаться от серверного, поэтому важно, какое из них мы используем для обращения к графикам. Из-за разницы между ними можно получить значения разных свечей. Например, при расхождении в 5 секунд – 15ч10м03с и 15ч09м58с – для минутного графика имеем в первом случае свечу 151000, а во втором – 150900. Причем, если первой еще не существует, получим нулевое значение. В представленной выше функции для чтения значений графиков используется серверное время, однако в любом случае целесообразно минимизировать расхождение между компьютером и сервером. Это можно сделать, настроив синхронизацию часов компьютера с каким-либо NTP-сервером. Очевидно, когда мы получаем нулевое значение графика, производить расчеты нельзя, поэтому сделаем проверку значения индикатора на отличие от нуля.


' Проверяем на отличие


от 0


IF EmaExp = 0


SET_ROW_COLOR_EX


(1, «RGB(255,128,0)»,


«DEFAULT_COLOR»,


«RGB(0,0,255)»,


«RGB(0,0,255)»)


RETURN


ELSE


SET_ROW_COLOR_EX


(1, «DEFAULT_COLOR»,


«DEFAULT_COLOR»,


«DEFAULT_COLOR»,«DEFAULT_


COLOR»)


END IF


Если значение равно 0, программа переходит к следующему циклу, при этом строка в таблице подсвечивается красным цветом, генерируя сигнал о том, что с графиком что-то не так.


Sh4Sp1.jpg


Стратегия


Когда значение «скользящей средней» получено, рассчитываем уровни покупки/продажи:


' Рассчитываем уровни


канала


LowLevel = EmaExp*(1-


Width/200)


HiLevel =


EmaExp*(1+Width/200)


При этом «ширину» канала будем задавать в процентах (переменная Width) от EmaExp. Величину защитного стоп-ордера также определяем в процентах от «ширины» канала (wStopLoss, см. F&O №11, 2011).


Width = 0.8 ' Ширина


канала в %


wStopLoss = Width*0.3 '


Ширина стоп-лосса – 30%


от канала


Далее сравниваем лучшие цены спроса/предложения с рассчитанными уровняуми и при выполнении условий осуществляем покупку/продажу. Соответствующий код программы приведен в справке.


Продажа контрактов происходит при выполнении нескольких условий (для покупки условия будут симметричными):


· Баланс по инструменту (nContract) больше или равен 0;


· Цена спроса (PriceBid) больше или равна расчетной цене верхнего уровня (HiLevel);


· Отсутствуют активные заявки по данному инструменту (FlagOrder=0).


Все сделки будут реверсивными, то есть при получении сигнала на продажу текущие длинные позиции (если таковые имеются) закрываются и сразу открываются короткие. Таким образом, при ценах выше/ниже канала позиция будет «переворачиваться», при этом мы все время будем находиться либо в длинной, либо в короткой позиции. Прибыль фиксируется при выходе цены за пределы канала: чтобы сделка закрылась в плюс, цена должна полностью пересечь заданный канал. Один из вариантов – фиксация профита на середине канала (то есть при достижении значения «скользящей средней» снизу или сверху), так мы увеличим количество удачных трейдов, но прибыль в каждой сделке уменьшится. Для этого варианта достаточно дополнить программный код модулем для закрытия позиций, текст которого приведен в справке.


Sh4Sp3.jpg


Результаты


Фактически мы имеем стратегию торговли по индикатору Envelope (конверт), только уровни рассчитываются самостоятельно, а не определяются исходя из графика. Пример работы робота представлен на Рисунке 2.


Рис. 2 Технический индикатор Envelope и сделки робота


Eroshkin2.png


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


· Временной интервал графика;


· Количество периодов и метод расчета «скользящей средней»;


· Период расчета алгоритма;


· Ширина (Width) канала (конверта);


· Глубина стоп-лосса (wStopLoss);


· Количество торгуемых контрактов (Unit);


· Величина проскальзывания (Slip).


Оперируя этими параметрами, можно варьировать работу робота в широких пределах – от единичных сделок в месяц до нескольких сотен в день (если ширину канала сделать очень узкой и минимизировать период расчета Moving Average). В рассмотренном примере мы использовали минутный график, экспоненциальную «скользящую среднюю» с периодом расчета 20, при этом ширина канала составляла 0,8%. Очевидно, что с увеличением временного интервала графика и количества периодов канал надо расширять, поскольку, чем больший промежуток времени мы рассматриваем, тем в более широком диапазоне движется цена. В любом случае подбор оптимальных параметров целесообразно проводить с помощью программ технического анализа на исторических данных (MetaStock, Omega и тому подобные).


LOG-файл


Как было отмечено ранее (F&O №10, 11, 2011), наш робот все свои активные действия записывает в LOG-файл (фрагмент представлен в справке). Используя сделанные записи, можно проанализировать работу программы.


При создании и отладке торгового автомата необходимо стремиться к тому, чтобы все его действия были понятны трейдеру. Если робот выполняет операции, которые не может объяснить его создатель, например, вдруг начинает выставлять ордера либо, наоборот, при наличии торговых сигналов бездействует, значит, присутствует ошибка или в коде программы, или в логике алгоритма. Крайне важно добиться от него предсказуемых действий, в противном случае можно рассчитывать на неожиданные результаты, причем, не всегда положительные.


Sh4Sp4.jpg


Заключение


Итак, мы создали полноценного торгового робота – программу, которая самостоятельно принимает решения об открытии позиций, выставляет заявки и отслеживает их исполнение. Если посмотреть на конечный код (полный код можно найти на сайте журнала www.fomag.ru), нетрудно заметить, что львиную долю занимают служебные функции, которые с успехом можно использовать при написании других торговых автоматов. Сама же стратегия занимает лишь около 10% кода.


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


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


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


Sh4Sp2.jpg


Скачать коды к Шагу 2


Скачать коды к Шагу 3


Скачать коды к Шагу 4


Литература:


1. А. Н. Пашаев, А. Ю. Ермошин, О. И. Малинкин, А. В. Морев, «Торговые системы Quik и NetTrader. Как начать», Омега-Л, 2007 г.


2. Ричард Вайсман, «Механические торговые системы. Психология трейдинга и технический анализ», Альпина Паблишер, 2011 г.


3. Джеффри Оуэн Кац, Донна Л. Маккормик, «Энциклопедия торговых стратегий», Альпина Бизнес Букс, Альпина Паблишер, 2007 г.


 


взято от сюда  fomag.ru/special_project/robot/






    Другие статьи по темам:
  • хорошо
    +2
    плохо

1 комментарий

avatar
круто! еще бы исходники в квик запихать и протестировать. ссылки битые на фомаг не пускает((

Добавить комментарий