Просьба ответить на мои вопросы из двух предыдущих постов. Если в документации неправильный пример, его точно надо оттуда убирать и, может быть, заменять на правильный.
Ещё вопрос о правильности примера в документации QLua, содержащегося в разделе с описанием функции CandleExist. Там есть пример, вычисляющий MovingAverage. Может быть, я чего-то не понимаю, но OnCalculate(index) может вызываться с одним и тем же индексом index несколько раз подряд на формирующейся свече. Кажется, что в этом случае в примере из документации среднее будет неправильным.
Функции O(I), H(I), L(I), C(I), V(I) в качестве индекса I используют порядковый номер свечи на графике, включая "дыры".
Вопрос:data source ds имеет ту же нумерацию ds:O(I), ds:H(I), ds:L(I), ds:C(I), ds:V(I) или здесь индекс I относится к источнику данных, где нет "дыр" между свечками?
В файловом архиве имеется файл ftp://ftp.quik.ru/public/INDICATORS.zip, который содержит код индикаторов технического анализа. Этот код новички могут рассматривать как пособие по созданию собственных индикаторов.
Однако, в связи с нововведением
Цитата
Изменен вывод информации функциями O, H, L, C, V, T по свечкам, сформированным на пустых интервалах. Теперь, для таких свечек, функция T возвращает время интервала, а функции O, H, L, C, V возвращают nil. Для корректной проверки существования свечи на графике добавлена новая функция CandleExist(). Подробное описание приведено в п. 7.2.5 Руководства пользователя Интерпретатора языка Lua.
в терминале QUIK 7.7, теперь на графиках бывают отсутствующие свечи со значениями nil в качестве цен. Соответственно, предложенные индикаторы перестают работать, т.к. раньше такого не было.
Не могли бы разработчики скорректировать исходный код этих индикаторов, чтобы они учитывали особенность нововведения?
У нас была похожая проблема: с началом торговой сессии останавливалась трансляция данных в таблицу обезличенных сделок. Решилась путём обновления серверного ПО (версия сервера QUIK от 7 декабря, кажется). Действительно, это может сделать только брокер.
Временное решение нашей проблемы было такое: в 10:00 МСК разорвать соединение с сервером и установить его снова.
Очень хорошо, что у Вас получилось воспроизвести ошибку "Неверный формат заявки". У нас она тоже изредка появляется, хотя мы не отправляем заявки из разных потоков, но в терминале одновременно работает несколько скриптов, каждый из которых шлёт заявки из своего main-потока. Теперь разработчики, скорее всего, быстро найдут и устранят причину (скорее всего, баг синхронного доступа к общему ресурсу). По нашему обращению не получилось этого сделать.
Вопрос к разработчикам: а где можно прочитать release notes по очередной версии терминала? Чтобы понимать, что изменилось, чему радоваться, а к чему относиться настороженно (вдруг какой-то баг закрался).
Философия проверки на отказы такая: если компонент по какой-то причине не сообщил вовремя, что он "живой", то компонент считается "погибшим".
Вы предложили правильное решение. Только скрипт или мониторинговую программу лучше запускать вне QUIK. Тогда это сработает, даже если сам QUIK упадёт, не отправив сообщение о падении своих lua-скриптов.
Женя написал: И еще такой момент, значение волатильности тоже "запаздывает"? Его тоже необходимо рассчитывать самому и использовать в формуле Б.-Ш.? Или на доске актуальное значение?
Значение волатильности тоже запаздывает. Правда, вред от его запаздывания сильно меньше, чем вред от резкого изменения цена базового актива. Волатильность потому и придумывали/используют, что она меняется заметно медленнее, чем цены опционов и базового актива.
Подскажите, где узнать, по какому принципу идет расчет на бирже? Используется ли для расчета крайнее или все же предыдущее значение волатильности?
Биржа рассчитывает волатильность и теоретическую цену не реал-тайм, а с некоторой периодичностью. Методика расчёта тут: http://fs.moex.com/files/4720
Для простых вариантов можно брать волатильность опциона из QUIK и оценивать теоретическую цену по формуле Б.-Ш. с использованием текущей цены. Для более сложных вариантов придётся самому брать биды/офера по страйкам и вычислять свою улыбку волатильности.
2016-09-14 13:26:11.668 [ERROR] QuotesExecutor:OnTransReply(): transId = 21371449, order_num=0, class_code=nil, sec_code=nil, status=11, balance=0, message=Не удалось сохранить транзакцию
При этом заявка на биржу не уходит. Логика программы-исполнителя заявок с этим справляется, но интересно, из-за чего такое случается? Что-то не так с сервером QUIK? Что скажут разработчики?
Я делаю бэк-тесты на java, а рабочие идеи программирую на qlua. Использую общую среду разработки IntelliJIdea (https://www.jetbrains.com/idea/).Там есть плагин для Lua (https://bitbucket.org/sylvanaar2/lua-for-idea/wiki/Home) с подсветкой синтаксиса, автодополнением, возможностями рефакторинга и синтаксическим анализом кода. Конечно, только для Lua установка такой IDE выглядит перебором, но если пишется код на java, то это неплохой универсальный вариант.
При использовании iup, если хотите в потоке main делать что-то ещё, а не только показывать окна и формы, стоит писать примерно так:
Код
while not interrupted do
iup.LoopStep() -- обеспечивает работу GUI
doSomeTrading() -- вычисление сигналов, постановка заявок
end
Вызов функции iup.MainLoop() лишает этой возможности.
При таком подходе, к сожалению, при выводе диалоговых окон и popup-меню работа программы приостанавливается: пока окно или меню на экране, doSomeTranding() не вызывается.
Выходит, что как-то можно пользоваться iup, но не всеми его возможностями. Так будет до тех пор, пока в qlua не появится возможность будет запустить отдельный вычислительный поток и там вызывать iup.LoopStep().
Вчера, 12.05.2016, произошла такая ситуация. В работающие в одном терминале (версия 6.17; версия сервера 5.2.6.118, аутсорсинг Арка) скрипты должен был придти коллбэк OnTrade().
В один скрипт, занимающийся мониторингом коллбэков, он пришёл (есть строка лога с параметрами OnTrade).
В другом скрипте, торговом, коллбэк OnTrade либо не вызывался, либо вызвался, но почему-то не дошёл до места, где формируется строка лога. Торговый скрипт интенсивно торгует уже более года и там нет ошибок в коде. Никаких выбросов исключений в потоке main, ошибок связи с интернетом зарегистрировано не было.
Это уже третий случай за последние три недели. Собственно, поэтому коллбэки мониторить и начали.
В связи с этим вопросы к разработчикам.
1) Может ли по какой-то причине не вызываться коллбэк OnTrade() в одном из скриптов на уровне терминала?
2) Правда ли, что в случае выброса исключений в коде функции коллбэка в потоке коллбэков скрипт должен упасть с выдачей какого-то сообщения об ошибке?
3) Может ли причина быть в связке терминал версии 6.17 и сервер версии 5.2.6.118? Посмотрите по серверным логам, если это возможно, что всё хорошо с отправкой информации по этой сделке.
Экспериментирую с библиотекой iup, чтобы понять, что можно, а что нельзя делать в qlua-скриптах с этой библиотекой. Обычные окна с элементами внутри них выводятся без проблем. Проблемы начинаются при показе модальных окон и popup-меню. Пока их не закроешь, поток main дальше не выполняется (видно по необновляющемуся заголовку окна и прерыванию записи в файл log.txt). Пример скрипта приведён в конце сообщения.
Вопрос к разработчикам QUIK и тем, кто смог решить подобные проблемы: можно ли добиться продолжения работы потока main и как это сделать?
В других языках программирования я бы выделил для uip отдельный вычислительный поток. Можно ли что-то подобное сделать в рамках QUIK?
Код
require("util.luapaths") -- мой код для задания package.cpath для подключения библиотек lua из дистрибутива LuaForWindows
require("iuplua")
local iup = iup
local interrupted = false
function OnStop()
interrupted = true
end
function main()
local btn1 = iup.button { title = "Show Dialog", }
function btn1:action()
local r = iup.Alarm("", "Main Title doesn't update!", "Exit")
if r == 1 then
interrupted = true
end
end
local btn2 = iup.button { title = "Show menu", }
function btn2:action()
local item = iup.item { title = "Exit", }
function item:action()
interrupted = true
end
local menu = iup.menu {
iup.item { title = "Dialog title doesn't update!", active = "NO", },
item
}:popup(iup.MOUSEPOS, iup.MOUSEPOS)
end
local dlg = iup.dialog {
iup.vbox { btn1, btn2 };
size = "EIGHTHxEIGHTH",
}
function dlg:close_cb()
interrupted = true
end
dlg:show()
local file = io.open("log.txt", "w+")
if file then
while not interrupted do
iup.LoopStep()
dlg.title = os.time()
file:write(os.time(), "\n")
file:flush()
sleep(100)
end
file:close()
end
dlg:destroy()
iup.Close()
end
Я провёл ещё один эксперимент на другом "свежем" инструменте, на стакан которого ещё не было подписок. Получается так, что при первом запуске скрипта приходит коллбэк OnQuote с первоначальным снапшотом стакана, а при повторных запусках скрипта уже нет, т.к. первый запуск скрипта сделал заказ данных по стакану.
Выходит, что алгоритм получения первоначального снапшота и последующих обновлений таков: 1) вызвать getQuoteLevel2(), который может вернуть нормальный стакан (первоначальный снапшот), а может и пустой, т.к. данные не заказаны; 2) вызвать Subscribe_Level_II_Quotes(), чтобы заказать данные, на всякий случай; 3) реагировать на OnQuote(), который вернёт первоначальный снапшот, если данных не было, и последующие обновления в любом случае.
local myClassCode = "SPBOPT" -- код класса
local mySecCode = "RI100000BC6" -- какой-нибудь РЕДКО меняющийся в стакане опцион
-- Предполагаем, что
-- 1) стакан по этому инструменту в терминале НЕ ОТКРЫТ
-- 2) Subscribe_Level_II_Quotes для этого инструмента не вызывался,
-- 3) известно (из другого терминала, например), что в стакане есть биды/офера
local interrupted = false
function OnQuote(classCode, secCode)
if classCode == myClassCode and secCode == mySecCode then
message("OnQuote(" .. classCode .. ", " .. secCode .. ")", 1)
end
end
function OnStop()
interrupted = true
end
function main()
-- Что сейчас в стакане? Надо, чтобы ничего не было, т.к. данные ещё не заказаны.
local q = getQuoteLevel2(myClassCode, mySecCode)
message("QuoteLevel2: bid_count=" .. tonumber(q.bid_count) .. ", offer_count=" .. tonumber(q.offer_count), 1)
-- Запрашиваем данные
Subscribe_Level_II_Quotes(myClassCode, mySecCode)
-- По идее, если в стакане стоят какие-то котировки,
-- через некоторое НЕПРОДОЛЖИТЕЛЬНОЕ время придёт снапшот стакана в OnQuote
while not interrupted do
sleep(1000)
end
end
Проверьте следующие пункты: 1) Время в долях года надо подставлять. Для RIH6 в данный момент (2016-03-09) это что-то типа 0.017. 2) Волатильность должна быть в долях, а не процентах. Для RIH6 на страйке 80 000 это что-то типа 0.385 сейчас.
Код
/**
* Цена опциона колл.
*
* @param underlying значение базового актива
* @param strike страйк
* @param t время до экспирации
* @param sigma волатильность
* @return цена опциона колл
*/
public static double callPrice(final double underlying, final double strike, final double t, final double sigma) {
if (t <= 0) {
return Math.max(underlying - strike, 0);
} else {
final double d0 = Math.log(underlying / strike);
final double a = sigma * sigma * t / 2;
final double b = sigma * Math.sqrt(t);
final double d1 = (d0 + a) / b;
final double d2 = (d0 - a) / b;
return underlying * n(d1) - strike * n(d2);
}
}
1) Если в стакане отсутствуют биды/офера, то в таблице, которую возвращает getQuoteLevel2 поле bid/offer является не таблицей, как написано в документации, а пустыми строками. Наверное, надо либо исправить документацию, либо QLua.
2) В документации в разделе "Функции для заказа стакана котировок" опечатка в последнем пункте (должно быть IsSubscribed_Level_II_Quotes):
3) Допустим, что я заказываю получение стакана по какому-нибудь малоликвидному опциону с помощью Subscribe. Как понять, что уже можно получать данные с помощью функции getQuoteLevel2? Эксперименты показали, что первое событие OnQuote придёт только после изменения стакана, а мне нужен и первый снапшот, как только данные стали доступными. Как понять: стакан пуст или ещё не пришли данные с сервера? Можно, конечно, долбиться раз в N секунд командой getQuoteLevel2 если стакан пуст, но как-то это неправильно выглядит.
Просьба к разработчикам прокомментировать написанное и принять меры, если это будет признано необходимым.
--- Функция распределения стандартного нормального закона.
-- @param x аргумент
-- @return значение
function n(x)
if x > 10 then
return 1.0
end
if x < -10 then
return 0.0
end
local ax = math.abs(x)
local t = 1.0 / (1.0 + 0.2316419 * ax)
local d = 1.0 / math.sqrt(2 * math.pi) * math.exp(-x * x / 2)
local p = d * t * ((((1.330274429 * t - 1.821255978) * t + 1.781477937) * t - 0.356563782) * t + 0.31938153)
if x > 0 then
return 1.0 - p
else
return p
end
end
--- Плотность нормального распределения.
-- @param x аргумент
-- @return значение
function phi(x)
return 1.0 / math.sqrt(2.0 * math.pi) * math.exp(-x * x / 2.0)
end
Практика показывает, что иногда новый релиз QUIK "сырой" или несёт в себе такие нововведения, которые могут дорого обойтись роботостроителям, если они не проверят совместимость своих систем с обновлением. Вот брокеры и перестраховываются, выкладывая обновления ПО после того, как пользователи-энтузиасты на свой страх и риск попробовали и НЕ выявили критичных проблем, а если выявили, то ну его нафиг такое обновление, чтобы брокер не оказался виноват.
В строке время сервера может замереть, а потом, через несколько секунд или даже минут быстро накрутиться до текущего времени, прям как таймер на бомбе). Или время проскакивает сразу по несколько секунд вперед. Отображение котировок в это время происходит соответственно - рывками, что негативно сказывается на результатах торговли.
У нас такие симптомы проявлялись из-за тормозов в интернет-модеме. На старте торгов и сам по себе он не успевал прокачивать данные. Сменили интернет-провайдера и расширили канал, всё стало нормально. Посмотрите в эту сторону.
> Хотелось бы понять, в правильном ли направлении я двигаюсь
В правильном. Главное -- написать какой-то стартовый вариант, запустить его, скажем, на QUIK Junior и проверять логику. Отладчик Decoda поможет. Постепенно основные проблемы проявятся, а Вы поймёте, как надо делать.
Я бы обратил внимание на следующее (если правильно понял код; не могу сказать, что сильно детально разбирался).
1) Если флаги OnOrder говорят, что "заявка выполнена", нет гарантии, что все OnTrade() дошли (бывает и такое).
2) robot:CheckTimeouts() вызывается в потоке main(), OnOrder(), OnTrade(), OnTransReply() -- в потоке коллбэков; при этом они вызывают некоторые общие функции -- можно попасть на ошибки из-за многопоточности. Лучше передать всю информацию из потока коллбэков в поток main() с помощью очереди и уже там работать. Очередь на Lua, думаю, сами напишете без проблем.
Ну, и мелочи:
1) os.time() выдаёт ответ с точностью до секунд; возможно стоит прикрутить более точный способ измерения времени (я использую Socket и gettime())
2) В QUIK есть функция bit.band() для проверки флагов (см. документацию) с помощью проверок типа bit_band(t.flags, 4) == 4.
Порядок OnTransReply -> OnTrade -> OnOrder обычно соблюдается, но может нарушаться. Программу надо писать так, чтобы она работала всегда. Вы ведь не хотите получить внезапно ошибку и убыток из-за неё.
Многократные ответы OnTrade() появились в 7-й версии. В повторных ответах могут добавиться некоторые поля.
Функция sendTransaction() может успешно выполниться, а OnTransReply() не придёт. Такое бывает, но очень редко. Однако, программу надо писать так, чтобы она работала всегда.
С асинхронностью нужно разобраться сразу и навсегда, чтобы не словить внезапных глюков. Один из способов такой: при получении коллбэка передавать данные через очередь из потока коллбэков в поток main(). В потоке main() периодически проверять очередь и обрабатывать данные, которые из неё приходят.
Проблему можно решить без участия команды QUIK. Надо прочитать документацию, разработать модель, запрограммировать её. Форум поможет при решении более тонких вопросов.
Основные шаги реализации примерно такие.
1) Нужны статусы заявок типа WAITING (ещё не отправлена), EXECUTING (отправлена на биржу), STOPPING (снимается, но не вся информация дошла), STOPPED (снята), EXECUTED (исполнена полностью), KILL_REJECTED (kill-заявка отвергнута), ERROR (ошибка).
2) Нужны таймауты для борьбы с неопознанными "посторонними" заявками (от других скриптов), а также заявками, для которых не пришёл OnTransReply().
3) Нужно следить за UID экземпляра QUIK, чтобы фильтровать "чужие" заявки.
4) Нужна функция генерации уникальных номеров транзакций, обеспечивающих непересекающиеся множества transId для разных скриптов.
5) Нужна таблица актуальных limit-заявок, куда заявка попадает при успешном вызове sendTransaction(), а удаляется при полном или частичном исполнении и ошибках.
6) Нужна таблица актуальных kill-заявок, куда заявка попадает при попытке снять limit-заявку, а удаляется при ошибках и исполнении.
7) Нужна таблица ответов о сделках на актуальные limit-заявки.
8) Нужен фильтр событий от "чужих" OnTrade(), например, работающий по комментарию к заявке.
9) В limit-заявке нужно помнить volume, volumeTraded, volumeLeft. Обычно volumeTraded + volumeLeft == volume, но иногда при снятии частично исполненной заявки становится понятно, чему равен volumeLeft и надо дождаться событий OnTrade(), которые ещё пока не пришли. При приходе OnTrade() нужно отбрасывать дубликаты (в 7-й версии) и корректировать volumeTraded, volumeLeft. Как только volumeLeft == 0, так удалять заявку из таблицы актуальных вместе со всеми связанными с ней kill-заявками и ответами на них.
10) При попытке снять limit-заявку иногда надо ждать, чтобы понять, какой order_num у отправленной заявки, поскольку ещё мог не придти ответ OnTransReply().
11) Периодически проверять, не стали ли известны order_num для лимитных заявок, для которых есть kill-заявки.
12) Периодически проверять, не стало ли известно, каким лимитным заявкам соответствуют OnTrade(), которые пришли раньше, чем OnTransReply().
13) Периодически разбираться с тем, сработали ли отправленные kill-заявки.
14) Удалять старые "неопознанные" сделки.
15) Реагировать на OnTransReply(), связывая transId скрипта и order_num биржи для limit-заявок. Понимать, как сработали kill-заявки. Проверять ждущие kill-заявки, если они есть.
16) Реагировать на OnTrade(), игнорируя дубликаты, логгируя соответствующие сделки.
17) Реагировать на OnOrder(), связывая transId скрипта и order_num биржи для limit-заявок. Понимать, что для некоторых уже неактивных заявок не дошли OnTrade() и логгировать сообщения об ошибках.
18) Периодически обрабатывать kill-заявки, чтобы понять, какие из них остались без OnTransReply(). Посылать новые, если что.
19) Периодически обрабатывать limit-заявки, чтобы понять, для каких из них получились кросс-сделки, а какие остались без OnTransReply(). Проверять, limit-заявки, которые почему-то неактивны, а volumeLeft не равен 0, и логгировать ошибки.
Ответы на многие вопросы проясняться, если Вы представите себе, что с помощью sendTransaction() с цикле отправляются несколько заявок по нескольким счетамиз нескольких разных скриптов одновременно, после чего каждый скрипт начинает получать в произвольном порядке коллбэки, причём каждый скрипт получает коллбэки от всего множества отправленных заявок.
Получится, что номера счетов могут быть разными, коды инструментов могут быть разными, какие-то заявки "свои для скрипта", а какие-то "чужие".
Дополнительно к этому добавляются специфические для QUIK проблемы типа несколько коллбэков OnOrder() подряд, в 7-й версии и OnTrade() тоже несколько для одной сделки, OnTransReply() может не придти при сетевых проблемах и т.д.
В общем, короче чем в 1000 строк удобный для последующего использования в роботах код для работы с заявками не получится.
Чтобы этот код написать, надо прочитать документацию, понять, как бороться с асинхронностью, разработать соответствующую модель. Это не просто, увы.
Тиковые данные по индексам, транслируемые в QUIK, могут не совпадать с теми, что скачиваются с сайта "Финама". См., например, индекс MICEX. У "Финама" есть некие объёмы, чего нет в QUIK (по крайней мере, у меня).
Насколько я знаю, возможность использования терминала 7-й версии завязана на то, использует ли брокер сервер Quik соответствующей версии (5-й, кажется).
Поскольку Вы попробовали и терминал 7-й версии заработал, то у брокера сервер его поддерживает.
Опыт показывает, что при выходе новой мажорной версии терминала, первые релизы сырые в том смысле, что присутствуют некоторые проблемы и ошибки (см. форум по этому поводу). Поэтому брокеры перестраховываются, чтобы клиенты потом не жаловались, что присутствуют проблемы и ошибки. Например, мой брокер пока не рекомендует использование новой версии терминала, но уже кажется, что скоро 7-й терминал будет рабочим.
В любом случае, надо проверять, корректно ли работают Ваши скрипты, т.к. в 7-й версии есть некоторые нововведения, которые заставляют изменять логику в некоторых местах кода роботов. Поскольку у каждого свои роботы, то у одного может всё работать без проблем, а у другого пойдут сбои. Конкретно мне пришлось бороться с тем, что события о сделках стали приходить в нескольких экземплярах.
В общем, проверьте логику работы, торгуя одним контрактом Сбербанка, чтобы сильно не влететь, если что-то пойдёт не так. Если работает несколько дней без проблем, то можно начинать более серьёзную торговлю.
Предположим, что скрипту на Lua в течение одной секунды по сигналам торговой системы нужно отправить 1000 лимитных заявок по разным инструментам и счетам, после чего успокоиться на 60 минут. Известно, что на бирже у брокера логин с производительностью 200 транзакций в секунду. Числа в этом примере условные.
Вопросы:
1) Что мы увидим: а) в скрипте Lua будут вылезать какие-то ошибки из-за большой частоты отправки заявок на сервер QUIK или б) сервер все эти заявки успешно примет в свою очередь и будет отправлять на биржу, ориентируясь на производительность логина брокера (при этом, понятное дело, ответы от биржи будут приходить в течение примерно 5 секунд).
2) Если мы увидим вариант 1а), то как понять (без экспериментов), с какой интенсивностью можно слать заявки из скрипта?
3) Если правильное функционирование сервера QUIK -- это вариант 1б), а ошибки внутри скрипта Lua вылезут, что делать (кто должен настраивать сервер QUIK, если он стоит на аутсорсинге ARQA)?
Главное, чтобы не получилось так: 1) Скрипт А создаёт DataSource, параметр добавляется. 2) Скрипт Б создаёт DataSource. 3) Скрипт А закрывает DataSource, параметр удаляется. 4) Скрипт Б обламывается.