На данный момент мы рассмотрели основные возможности языка Lua для создания торговых роботов. Чтобы скрипт на языке Lua мог взаимодействовать с терминалом QUIK, в терминале существует интерпретатор языка Lua под названием QLua. QLua это набор функций, при вызове которых происходит определенное действие. Функции интерпретатора QLua вызываются с определенными параметрами, и в зависимости от параметров, указанных при вызове, совершают определенное действие или возвращают определенное значение. Все функции интерпретатора QLua прописаны в документации по языку Lua в QUIK, файл документации можно скачать на официальном сайте QUIK www.arqatech.com или найти в документах данного курса.

Работа торгового робота заключается в следующем. С помощью интерпретатора QLua робот получает необходимую информацию из терминала QUIK, далее робот при помощи языка Lua обрабатывает полученные значения и в зависимости от результата обработки отправляет в терминал QUIK поручение, на совершение сделки, используя интерпретатор QLua. Как обрабатывать информацию при помощи языка Lua нам уже известно. Теперь разберемся с получением информации из терминала QUIK. Для изучения функций получения данных из терминала QUIK будем использовать фьючерс на индекс RTS – RIZ7.

Основной код скрипта для терминала QUIK пишется в специальной функции под названием main(). Функция вызывается терминалом QUIK, и выполняется в отдельном потоке, не связанным с потоком выполнения программы терминала QUIK. Скрипт считается запущенным, пока выполняется функция main(), как только выполнение функции main() заканчивается, скрипт считается остановленным.

Записывается функция main(), как обычная функция:

function main()

end

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

Для начала создадим окно, в программе QUIK, в которое, будем выводить необходимые значения инструмента. Окно будет в виде таблицы с одной строкой и несколькими столбцами. Для создания окна в виде таблицы используется функция AllocTable(), данная функция описывает структуру таблицы и возвращает числовой идентификатор таблицы, по данному идентификатору мы будем обращаться к данной таблице, чтобы внести в нее изменения.

Создадим новый скрипт с именем «014 Пользовательские таблицы.lua»

Пропишем функцию main(). Создадим переменную с именем TableRTS и поместим в нее числовой идентификатор, используя функцию AllocTable(). Функция AllocTable() возвращает числовой идентификатор таблицы. С помощью числового идентификатора мы будем заполнять и редактировать нашу таблицу.

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

Назовем их следующим образом:

Дата – Дата.
Время – Время.
Код торгового инструмента — Код.
Цена последней сделки – Цена.
Гарантийное обеспечение – ГО.
Плановые чистые позиции – Лимит, руб.
Количество контрактов, которое мы можем купить – Контракты.
Текущая позиция – Позиция.

Для добавления колонок в окно таблицы используется функция AddColumn() принимающая на вход следующие обязательные значения:

1.Идентификатор таблицы (в данном случае TableRTS).
2.Код параметра выводимого в колонке (задается вручную, в данном примере коды будут начинаться с 1 и заканчиваться на 8, поскольку в таблице будет восемь колонок).
3.Название колонки (первая колонка будет называться «Код», далее по списку).
4.Не используемый, но обязательный параметр – его значение всегда будет true.
5.Тип данных содержащихся в колонке. Данный параметр проставляется в зависимости от вида данных отображаемых в колонке и может принимать несколько значений, мы будем использовать следующие значения:
— QTABLE_INT_TYPE – целое число;
— QTABLE_DOUBLE_TYPE – число с плавающей точкой;
— QTABLE_STRING_TYPE – строковое значение;
— QTABLE_DATE_TYPE – дата;
— QTABLE_TIME_TYPE – время.
6.Ширина колонки в условных единицах – подбирается методом проб.

Добавим строки.

Структура окна таблицы создана, теперь для отображения окна таблицы в терминале QUIK создадим таблицу с помощью функции CreateWindow() (создать окно), передадим в функцию идентификатор окна таблицы TableRTS.

Чтобы добавить заголовок окну таблицы TableRTS используем функцию SetWindowCaption (установить заголовок окна), передадим в функцию идентификатор окна таблицы и строковое название.

Чтобы таблица при запуске скрипта отображалась в нужном месте окна QUIK, установим координаты таблицы. Для этого необходимо указать координаты верхнего левого угла окна и нижнего правого угла окна в пикселях.

Верхний левый угол будет иметь координаты 0 по ширине окна QUIK и 10 по высоте окна QUIK, нижний правый угол будет иметь координаты 600 по ширине окна QUIK и 70 по высоте окна QUIK. Координаты окна задаются с помощью функции SetWindowPos(), в которую передается идентификатор окна таблицы TableRTS и координаты углов.

Получить координаты таблицы можно вызвав функцию GetWindowRect(), передав в нее идентификатор окна таблицы TableRTSGetWindowRect(TableRTS) — Функция вернет таблицу Lua содержащую координаты верхнего левого угла и нижнего правого угла указанного окна таблицы.

Последним штрихом в создании окна таблице будет добавление строки в которую будут выводится необходимые значения. Для добавления строки в таблицу используется функция InsertRow(), в функцию передается идентификатор окна таблицы и ключ строки. При добавлении в таблицу первой строки используется значение «-1», строка добавиться в конец таблицы. Если необходимо в конец таблицы добавить строку, то также используется данное значение.

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

Запустим скрипт в терминале QUIK и посмотрим, как отображается окно таблицы. Для запуска скрипта используем меню «Сервисы» пункт меню «Lua скрипты…» (Сервисы > Lua скрипты…). Откроется окно «Доступные скрипты», нажмем кнопку «Добавить», выберем скрипт и откроем его.

В окне «Доступные скрипты» появиться выбранный скрипт, с лева от него отображается значок Stop – означающий, что скрипт остановлен, если скрипт запущен, отображается значок Play. Ниже списка скриптов расположено окно «Ошибки выполнения скрипта», в котором отображаются ошибки в скрипте. Ошибки описываются достаточно подробно, прописывается имя и путь к скрипту, номер строки, в которой возникла ошибка и описание ошибки. Выберем в окне «Загруженные скрипты» наш скрипт и нажмем кнопку «Запустить», скрипт запуститься выведет на экран таблицу и остановиться.

На данный момент это все, что от него требовалось. 

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

Откроется «Информационное окно», в котором отображаются различные параметры, для того, чтобы взять параметр из информационного окна используется функция QLua с именем getInfoParam(), при вызове функции в нее передается имя параметра, который необходимо получить. Список все имен параметров указан в документе «Интерпретатор языка Lua». На данный момент нас интересует два параметра это, дата торгов – имя TRADEDATE и время сервера – SERVERTIME.

Создадим две переменные и поместим в них соответственно дату торгов и время сервера, вызвав функцию getInfoParam():

Теперь переменные содержат необходимые значения. Заполним первые две колонки таблицы, установив для них соответствующие значения. Для заполнения колонок в окне таблицы используется функция SetCell с определенными параметрами. В функцию необходимо передать идентификатор окна таблицы, номер строки, код колонки (который мы указывали при создании колонки) и значение, преобразованное в строковый тип.

Выведем значения в колонки:

Сохраним и запустим скрипт в терминале QUIK.

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

Далее, заполним следующие колонки окна таблицы.

Код бумаги – это сокращенное название торгового инструмента, в данном случае RIZ7. Создадим строковую переменную, которая будет содержать Код бумаги. local SecCode = «RIZ7»

Цена последней сделки и размер гарантийного обеспечения содержатся в таблице терминала QUIK под названием «Текущие торги», для того чтобы обратиться к данной таблице используется функция QLua под именем getParamEx(). Для вызова функции в нее необходимо передать следующие параметры:

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

Имя параметра – это специальный идентификатор параметра. Идентификаторы параметров таблицы «Текущие торги» прописаны в руководстве к терминалу QUIK в файле «8 Язык QPILE.pdf» страницы с 63 по 68. Почему идентификаторы не добавлены в руководство по интерпретатору QLua – не знаю.

Вызвав функцию getParamEx(), с необходимыми параметрами, в ответ мы получим не одно значение, а таблицу, содержащую следующие строки.

Зная, что функция getParamEx() возвращает таблицу, объявим пустую таблицу с именем LastTable для параметра «Цена последней сделки».

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

Разберем это строку. Функцией getParamEx() мы обращаемся к таблице «Текущие торги», далее в круглых скобках указываем параметры. Первое, говорим, нам нужны фьючерсы «SPBFUT», второе говорим код бумаги «SecCode» (в переменную «SecCode» мы записали код бумаги), третье говорим нам нужна цена последней сделки инструмента «LAST». Функция getParamEx() возвращает нам таблицу в которой содержится тип данных запрашиваемого параметра » param_type», сам параметр «param_value», строковое значение параметра «param_image» и результат операции запроса «result».

Далее, чтобы получить значение цены последней сделки, возьмем значение из строки с ключом param_value таблицы LastTable и поместим в новую переменную LastPrice.

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

Запишем следующую строку:

local LastPrice = getParamEx(«SPBFUT», SecCode, «LAST»).param_value

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

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

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

Добавим строки заполняющие колонки окна таблицы для трех параметров: Код бумаги, Цена последней сделки, Гарантийное обеспечение.
В колонку Код бумаги выведем переменную его содержащую SecCode.
В колонку Цена выведем цену последней сделки, полученную из таблицы «Текущие торги», переменная LastPrice.
В колонку ГО выведем гарантийное обеспечение покупателя, полученное из таблицы «Текущие торги», переменная GO.

Сохраним и запустим скрипт в терминале QUIK.

В колонках Цена и ГО отображаются точки вместо значений. Раздвинем колонки и увидим, что значения содержат много нулей после запятой, которые нам не нужны.

Отформатируем вывод значений в колонки окна таблицы.

В колонке Цена, нам необходимо убрать все значения после запятой, поскольку цена на фьючерс индекса RTS не имеет дробной части. Изменим вывод, используя отбрасывание дробной части числа, в строке с выводом в колонку Цена.
В колонке ГО будем отображать два знака после запятой.
Как взять определенное количество значений после запятой рассказано а разделе «3. Арифметические операторы«.

Сохраним и запустим скрипт в терминале QUIK.

После форматирования, значения в колонках «Цена» и «ГО» выводятся с нужным количеством знаков после запятой.

Заполним колонку «Лимит, руб.»
Для заполнения колонки «Лимит, руб.» необходимо взять из окна «Ограничения по клиентским счетам», значение из колонки «Плановые чистые позиции». Данный параметр отображает количество доступных денежных средств на открытие позиции, т.е. на какую сумму мы можем купить/продать фьючерсов. Чтобы обратиться к таблице «Ограничения по клиентским счетам», используем функцию QLua с именем getFuturesLimit(), функция возвращает таблицу Lua содержащую все колонки указанной строки. В окне «Ограничения по клиентским счетам» могут отображаться несколько счетов одного клиента, если у клиента открыты субсчета. Также в данной таблице отображаются не только чистая денежная позиция по счетам, но и клиринговая денежная позиция. Нас будет интересовать только чистые денежные средства на счетах. При вызове функции getFuturesLimit(), указывается конкретный счет клиента, по которому возвращаются все доступные значения. Вызовем функцию, функция вернет таблицу, из таблицы возьмем строку, содержащую значение «Плановые чистые позиции». Ключи ко всем строкам возвращаемой таблицы указаны в документе «Интерпретатор языка Lua», таблица «4.9 Лимиты по фьючерсам».

Разберем значения, переданные в функцию getFuturesLimit().

«SPBFUT» – код фирмы, он же код класса.
«SPBFUT00000» – торговый счет клиента.
0 – тип лимита, денежные средства.
«SUR» – валюта денежных средств, рубль.
.cbplplanned – из возвращаемой таблицы Lua взять значение из строки с ключом cbplplanned (Плановые чистые позиции).

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

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

local Kont = Limit/GO

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

Kont = Kont – Kont%1

Отобразим значения Limit и Kont в соответствующих колонках нашей таблицы.

SetCell(TableRTS, 1, 6, tostring(Limit))
SetCell(TableRTS, 1, 7, tostring(Kont))

Запустим скрипт в терминале QUIK.

В соответствующих колонках отобразились необходимые значения.

Осталась, не заполнена последняя колонка, содержащая текущую чистую позицию по инструменту.
Текущая чистая позиция по инструменту содержится в окне таблицы QUIK под названием «Позиции по клиентским счетам (фьючерсы)», чтобы обратиться к данной таблице используется функция QLua с именем getFuturesHolding(), функция возвращает таблицу Lua содержащую все доступные данные из указанного окна таблицы. Ключи к строкам возвращаемой таблицы указаны в документе «Интерпретатор языка Lua.pdf» в разделе «4.10 Позиции по клиентским счетам (фьючерсы)».

Окно таблицы «Позиции по клиентским счетам (фьючерсы)» может содержать несколько строк с информацией о позициях по различным счетам и инструментам. Для вызова функции в нее необходимо передать информацию об идентификаторе класса бумаги, номер счета клиента, код бумаги и тип лимита (тип лимита указывает на характер счета по которому берется информация, параметр может содержать числовое значение от 0 до 3, 0 – Основной счет, 1 – Дополнительный или субсчет, 2 – Все счета членов, 3 – пусто). Вызовем функцию getFuturesHolding(), одновременной возьмем из возвращаемой таблицы значение строки «Текущие чистые позиции» с ключом totalnet и поместим результат в новую переменную Pos.

Сохраним и запустим скрипт в терминале QUIK.

Программа выдала ошибку «:24: attempt to index a nil value».
:24: – номер строки с ошибкой, комментарий к ошибке «обращение к индексу с значением nil«.

Ошибка появилась из-за того, что в окне таблицы «Позиции по клиентским счетам (фьючерсы)» отсутствует строка с заданными параметрами. Строка отсутствует, по причине того, что мы не совершали сделок с фьючерсом RIZ7 в текущей торговой сессии. Чтобы исключить возможность возникновения данной ошибки изменим код и добавим условие проверки.Подкорректируем строку

local Pos = getFuturesHolding(«SPBFUT», «SPBFUT00p68», «RIU6», 0).totalnet,

удалив из нее .totalnet, теперь переменная, будет таблицей, поскольку функция getFuturesHolding() возвращает таблицу. Если при вызове функции getFuturesHolding(), строка с указанными параметрами не будет найдена, то Pos будет nil.

Добавим условие:

Если Pos не равно nil, то берем из возращенной таблицы Pos строку по ключу totalnet и полученное значение записываем в переменную Pos, иначе присваиваем переменной Pos значение 0. В языке Lua допускается преобразование переменной в таблицу и наоборот.

Выведем значение переменной Pos в столбец «Позиция».

Сохраним и запустим скрипт.

Все столбцы заполнены, ошибок нет.

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

Первым делом предусмотрим, как будет разрываться наш цикл. Добавим логическую переменную, которая будет менять свое значение, если необходимо остановить цикл. Добавим ее не в тело функции main() а выше, в первую строку кода. Это необходимо, чтобы переменная была видна не только в функции main(), но и в дополнительной функции интерпретатора QLua — OnStop().

Функция OnStop() является функцией обратного вызова, и вызывается автоматически терминалом QUIK если происходит действие – нажатие на кнопку «Остановить» в окне «Доступные скрипты». Функциями обратного вызова являются функции интерпретатора QLua, которые вызываются автоматически терминалом QUIK, при изменении, какого-то параметра или совершение пользователем, какого-то действия. Мы еще не раз столкнемся с функциями обратного вызова. В документе «Интерпретатор языка Lua.pdf» есть описание всех функций обратного вызова.

Вернемся к нашей функции OnStop(). Создадим ее.

Функция OnStop() будет менять значение переменной stopped на true, чтобы остановился основной цикл программы и возвращать 5000 миллисекунд (5 секунды) времени для того, чтобы код программы успел завершить все свои действия. После истечения 5000 миллисекунд, код программы будет остановлен принудительно.

Вернемся к нашей таблице TableRTS.

Нам нужно чтобы значение в таблице периодически обновлялось. Для периодического обновления значений добавим цикл while. В цикл необходимо поместить код, который требуется выполнять многократно. В нашем случае вставим цикл while после InsertRow(TableRTS, -1) (строка кода номер 21), оператор конца цикла end поместим перед концом функции main(). Также необходимо задать частоту итераций цикла. Пусть основной цикл while выполняет свой код два раза в секунду. Для этого необходимо добавить в цикл паузу. Перед оператором end цикла while вставим следующую строку кодаsleep(500)Оператор sleep останавливает выполнение кода на указанное в миллисекундах время.

Рассмотрим подробнее работу цикла в данном примере. Цикл while (строка кода 22), будет выполняться до тех пор, пока переменная stopped будет равна false. Переменная stopped изменит свое значение на true, когда мы остановим скрипт кнопкой «Остановить» в окне «Доступные скрипты». Оператор sleep(500) (строка кода 46), останавливает выполнение скрипта на 500 миллисекунд (0,5 секунды) следовательно, цикл будет повторяться два раза в секунду. Если не предусмотреть паузу между итерациями цикла, то частота итераций будет слишком большой.

Сохраним и запустим скрипт в терминале QUIK.

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

Обратите внимание на значок «Play» напротив названия скрипта в окне «Доступные скрипты», этот значок означает, что скрипт запущен и в данный момент работает.

Остановим скрипт кнопкой «Остановить» в окне «Доступные скрипты».

Добавим вывод информационного сообщения с предупреждением об остановки скрипта и поместим в окно «Таблица параметров RIZ7» в столбец «Дата» текст «STOP», чтобы посмотрев на окно можно было определить работает скрипт или нет.

Для того чтобы вывести на экран информационное сообщение используется функция QLua – message().Вставим вызов функции message() после оператора end цикла while и до оператора end функции main().

Для изменения значения в колонке «Дата» используем уже известную функцию SetCell(). Ниже строки с выводом сообщения добавим строку, которая выводит в столбец «Дата» нашей таблицы текст «STOP».

Сохраним и запустим скрипт в терминале QUIK и через несколько секунд работы остановим его кнопкой «Остановить».

На экране появилось окно сообщения с текстом «Скрипт 014 Пользовательские таблицы.lua ОСТАНОВЛЕН.» в колонке «Дата» значение изменилось на «STOP».

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

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

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

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