Очень хорошо, что у Вас получилось воспроизвести ошибку "Неверный формат заявки". У нас она тоже изредка появляется, хотя мы не отправляем заявки из разных потоков, но в терминале одновременно работает несколько скриптов, каждый из которых шлёт заявки из своего 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) Скрипт Б обламывается.
Про nil, его нет и не было, мы зарегистрировали пожелание. Дословно оно звучит так "чтобы во всех колбеках возвращающих таблицы, при отсутствии значения параметра передавать nil вместо "0"
Замечательно, главное, чтобы реализация была в версии 7.0.1.x или 7.0.2.x.
Цитата
На что не однократно прозвучал ответ, что в такой реализации для отправки trans_id=0 будет лишний колбэк, которого сейчас нет. А лишние колбэки, судя по этой ветке форума, никому не нравятся.
Лишний коллбэк с trans_id = 0 для (гарантировано) ручной сделки мы потерпим.
Цитата
Соответственно мы предлагаем зарегистрировать пожелание в общих терминах, без озвученной конкретики и подумать над альтернативным решением, каким именно пока не понятно.
Плохо понятно, что имеется в виду. Со стороны программиста хотелось бы: 1) nil в полях таблицы коллбэка, если сервер хочет сообщить нам некую информацию, но ещё не вся таблица коллбэка заполнена по каким-то причинам (нет информации; чтобы не ждать, пока сервер что-то найдёт у себя во внутренних структурах); 2) не получать повторный коллбэк, если для пользователя набор значений в полях таблицы не меняется.
Предположим, что имеются две таблицы, dt1 и dt2, содержащие моменты времени (поля year, month, day, hour, min, sec). Сначала с помощью функции os.time вычислим, сколько секунд прошло с начала эпохи до этих моментов времени:
Код
local t1 = os.time(dt1)
local t2 = os.time(dt2)
Затем определим, сколько секунд прошло между этими моментами времени с помощью функции os.difftime:
Код
local duration = os.difftime(t2, t1) -- под Windows можно просто отнять от t2 значение t1
Теперь находим, сколько прошло часов, минут и секунд между t1 и t2. При этом значение переменной duration будет изменяться.
Код
local seconds = duration % 60
duration = (duration - seconds) / 60
local minutes = duration % 60
duration = (duration - seconds) / 60
local hours = duration
Согласен с тем, что мирное решение проблемы состоит в нормальной документации того, что и как может меняться в списке параметров. ARQA пока не хочет это делать, чтобы не быть связанной какими-либо обязательствами, перебрасывая проблемы на пользователей.
У себя в коде я сделал так: если приходит OnTrade, в котором нет всех параметров, необходимых для обработки события, игнорируем это событие и надеемся, что придёт ещё одно. Обрабатываем заполненное событие как и раньше (молясь о том, что все необходимые параметры заполнены окончательными значениями; список приведён мною раньше; от ARQA достаточно подтверждения, что эти параметры заполняются синхроно и окончательно). Значения uid, trans_id ищу сам, хотя сейчас будет помощь от Quik, но код переписывать уже не хочется (новички, кто ещё не писал исполнителя заявок, правда, это может и оценят). При этом запоминаю ключи class_code.order_num для фильтрации повторных уже не нужных OnTrade.
По моим оценкам задержка при получении повторных коллбэков с trans_id примерно те же, что и при получении OnOrder с этой информацией. Т.е. быстрее реально не стало. Из-за повторов событий о сделках код даже стал медленнее, т.к. приходится делать дополнительные проверки и фильтрацию.
В шестой версии такого не бывает, т.к. она не поддерживает обновление строк в таблице сделок. Про это Горохов писал.
В седьмой версии, как мне кажется, такого быть также не должно, но спецификация, которую разработчики quik предлагают нам (любые поля могут обновляться сколько угодно раз) приводит к тому, что невозможно разрабатывать по ней скрипты, хоть и удобно для ARQA с точки зрения минимизации своей ответственности. Примеры Вы (реальный) и я (гипотетический, но по спецификации) привели.
Гарантий нет, всё делаем на свой страх и риск. Занимаемся эмпирическим программированием. Конечно, такое отношение к пользователям не радует. А то, что пишет Горохов - отдельная беда, хотя может у него задача такая - держать первую линию обороны от недовольных пользователей.
Sergey Gorokhov пишет: Потому что пользователи хотели чтобы на сделках был TRANS_ID и другие параметры
OnTrade всегда приходит с заполненным полем trans_id? Если trans_id=0 в сделке, означает ли это, что сделка была совершена вручную? Или нужно подождать второго-третьего колбека, чтобы убедиться в этом?
Нет, trans_id может быть сначала 0, если заявка быстро исполнилась на бирже. Потом, в последующих коллбэках, будет выставлено правильное значение trans_id. Если сделка была ручная, то trans_id останется нулём. Придёт ли в этом случае второй-третий коллбэк -- непонятно.
Вообще, раньше OnTrade был самым надёжным коллбэком (OnTransReply может не придти, OnOrder приходит несколько раз и, обычно, с запозданием), а теперь и OnTrade испортился. Ситуация стала похожа на вопрос о том, закрылась очередная свеча или нет.
Воистину, думай о том, что будет, если твоё желание исполнится (тем пользователям, которые хотели trans_id в сделке). От реализации такого пожелания стало хуже, если опираться на то, что разработчики гарантируют.
Вообще, было бы круто реализовать в lua абстракцию, где можно оперировать заявками-объектами, ставить их, снимать (даже если от биржи ещё не пришёл order_num). При этом вся кухня с trans_id и т.п. была бы скрыта от пользователя в недрах терминала. Но, к сожалению, разработчики просто поставляют пользователям данные, как получится, пусть пользователи сами разбираются.
Чисто технически, да могут поменяться Чисто практически, такая ситуация маловероятна
Это плохо, если разработчики говорят в терминах вероятностей о поведении программы.
Представим, что пришла информация о сделке, где комиссии указаны нулевые. Это может произойти, если сделки по фьючерсу идут внутри дня. Адекватная торговая программа пишет в какой-нибудь лог информацию о сделке с нулевой комиссией, а потом ВНЕЗАПНО обнаруживается, после прихода ещё одного коллбэка, что комиссии-то ненулевые! Вот засада и головная боль для программиста! Надо либо в лог дописать, что, мол, фигня вышла, вот правильная информация о сделке, либо действовать по принципу: хочешь записать что-то в лог -- подожди некоторое время в надежде, что вдруг всё не так на самом деле.
А хорошо -- это описать, что такие-то и такие-то блоки данных изменяются атомарно и однократно. В документации это будет?