Подтверждаю, что OnTransReply может не придти. Хотя это бывает очень редко, при сетевых проблемах, но проектировать логику отслеживания статусов заявок надо с учётом этого.
Порядок прихода коллбэков может быть любым. Это тоже надо учитывать.
Может, я что-то не так понял, но у меня используется код такого типа:
Код
local NaN = 0 / 0
local function getPointsToRublesMultiplier(classCode, secCode)
if InfoSecurities.isFuturesClass(classCode) then
local stepPrice = getParamEx(classCode, secCode, "STEPPRICE")
local secPriceStep = getParamEx(classCode, secCode, "SEC_PRICE_STEP")
if type(stepPrice) ~= "table" or type(secPriceStep) ~= "table" then
return NaN
end
stepPrice = stepPrice.param_value
secPriceStep = secPriceStep.param_value
if stepPrice == nil or secPriceStep == nil then
return NaN
end
local stepPriceInRub = tonumber(stepPrice)
local priceStepInPts = tonumber(secPriceStep)
if stepPriceInRub > 0 and priceStepInPts > 0 then
return stepPriceInRub / priceStepInPts
else
return NaN
end
else
return 1
end
end
Я имел в виду, что настройки шрифта ПО УМОЛЧАНИЮ для диаграмм можно дать в диалоге "Настройки клиентского места". Например, хочет пользователь, чтобы все НОВЫЕ диаграммы были с шрифтом Segoe UI размера 10, а у него каждый раз вылезает Arial 8 и непонятно, где установить такую настройку. Придётся КАЖДЫЙ РАЗ редактировать КАЖДЫЙ новый график.
По нормальному и надёжно -- никак. У МосБиржи и есть параметр, транслируемый в ТТП в колонке "Статус" ("status"), где пишется "торгуется" или "приостановлено", бывает (при авариях на бирже), что этот статус остаётся "торгуется", хотя реально всё встало. И биржа особенно не заботится об этом. Если вы влетите на штраф за ошибочные транзакции -- это уже ваши проблемы.
Если инструмент ликвидный и сделки идут часто, можно смотреть, сколько прошло времени с момента последней сделки. Если более, скажем, 60 секунд, значит при выставлении своей заявки надо задуматься, а стоит ли.
В случаях аварий на бирже можно вести аналогичные измерения для классов инструментов. Скажем, если по SPBFUT более минуты нет сделок, а должны быть, то это либо клиринг, либо авария какая-то.
У меня в скриптах отслеживаются как статус, так и таймауты. Отлажено на реальных событиях :)
Берём терминал 7.16.3.14, нажимаем F9 и получаем окно с настройками клиентского места. Там есть пункты Программа -> шрифты, где можно указать шрифты для заголовков столбцов, строк, числовых данных и текстовых данных, а также для окон диалогов. Кнопка "Стандартные" устанавливает везде шрифт Segoe UI.
Однако, тут нет пункта для установки шрифта для диаграмм. У меня диаграммы создаются со шрифтом Arial, который потом можно заменить на Segoe UI.
Кажется, что здесь некоторая недоделка. По-хорошему, шрифт диаграмм также должен быть в настройках клиентского места.
Раз уж QPILE больше не развивается и его всё больше заменяет QLua, давайте перенесём комбинацию Ctrl+F11 с пункта меню "QPILE скрипты" на пункт меню "Lua скрипты". Полагаю, что пользователей, довольных этим нововведением, будет больше, чем недовольных, а реализация от разработчиков терминала практически не потребует усилий.
В каждую из папок устанавливаете соответствующий дистрибутив программы, рекомендованный каждым из брокеров (версии могут быть разными в зависимости от брокера).
Пользуетесь терминалами независимо друг от друга, возможно одновременно.
Для старта этого достаточно. Если хочется одинаковые настройки в каждом из терминалов, придётся делать дополнительные действия и рисковать несовместимостью wnd-файлов настроек из-за разных версий терминалов.
Это не сообщение о какой-то ошибке или проблеме, наоборот, это сообщение, которое может помочь программирующим на qlua скрипты роботов.
Допустим, что Ваш скрипт создаёт одно или несколько окон на своей вкладке. Если не прилагать усилий и просто создавать окна через CreateWindow, эти окна получают некоторый размер и местоположение, определяемые автоматически. Не всегда это удобно. Можно после создания указать конкретное местоположение и размеры окна с помощью функции SetWindowPos(tableId, x, y, dx, dy). Однако, откуда взять значения x, y, dx, dy? Их можно подбирать методом последовательных приближений, но ведь это неудобно!
Предлагается следующий подход. Сначала Вы располагаете графики, окна Вашего скрипта и прочие элементы так, чтобы было удобно. Потом запускаете предлагаемый ниже код. Он создаёт пустое окно, которое можно перемещать и изменять его размер с помощью мышки, накладывая это окно ровно поверх каждого окна Вашего скрипта. При этом в заголовке накладываемого окна динамически обновляются параметры x, y, dx, dy, которые нужно будет подставить в функцию SetWindowPos(tableId, x, y, dx, dy) в Вашем скрипте. При этом подбор параметров можно осуществить сразу, а не методом последовательных приближений.
Вот такой вот калибровщик положения и размеров окон получается.
Если я изобрёл велосипед, а все нормальные программисты пользуются подобными штуками, отнесусь к этому с пониманием.
Код
--
-- Подгонка размеров окна.
--
local interrupted = false
function OnStop()
interrupted = true
end
function main()
local tId = AllocTable()
CreateWindow(tId)
local topPrev, leftPrev, bottomPrev, rightPrev = 0, 0, 0, 0
while not interrupted do
if IsWindowClosed(tId) then
break
end
local top, left, bottom, right = GetWindowRect(tId)
if top == nil or left == nil or bottom == nil or right == nil then
break
end
if topPrev ~= top or leftPrev ~= left or bottomPrev ~= bottom or rightPrev ~= right then
SetWindowCaption(tId, "x=" .. tostring(left)
.. ",y=" .. tostring(top)
.. ",dx=" .. tostring(right - left)
.. ",dy=" .. tostring(bottom - top))
topPrev = top
leftPrev = left
bottomPrev = bottom
rightPrev = right
else
sleep(50)
end
end
end
В принципе, ничего не мешает в каждом коллбэке написать проверку типа: если очередь на выполнение, пополняемая из потока main непуста, выполнить функции из неё. У этого подхода есть недостатки: 1) придётся написать в каждом коллбэке строчку, вызывающую работу с очередью функций; 2) если рыночных коллбэков не происходит (например, торги остановились), main не получит требуемый результат, хотя поток коллбэков свободен.
Просьба к разработчикам терминала обратить внимание на это сообщение и дать свои конструктивные комментарии.
Допустим, что у нас есть экземпляр DataSource, который содержит в себе 5-минутные свечки:
Код
local ds = CreateDataSource(classCode, secCode, INTERVAL_M5)
Если ds используется в потоке коллбэков, то есть гарантия, что пока в коде коллбэка идёт работа с этим экземпляром (например, итерирование по индексу от 1 до ds:Size()), данные внутри него (количество свечей, их high, low, close, volume) не меняются.
Вопрос в том, как добиться стабильности внутреннего состояния в main-потоке? Ведь если я запомню в переменной размер DataSource
Код
local size = ds:Size()
перед началом цикла, то в процессе итерирования могут как добавиться новые свечи, так и обновиться старые (скажем, сначала обновилась последняя свеча, а потом добавилась новая). При этом могут возникать неприятные эффекты типа в потоке main прочитали high последней свечи, потом поток коллбэков обновил свечу так, что новое значение close стало больше уже прочитанного значения high, а потом поток main увидел последнее значение close, которое больше прочитанного ранее high.
Не уверен, что будет происходить с содержимым DataSource при смене торговой сессии, скорее всего, ничего хорошего.
Чтобы гарантированно иметь консистентные данные, можно, например, использовать ds:SetUpdateCallback, в котором заранее производить копирование состояния ds или его изменений в другой объект, и складывать в очередь, разгребая которую из потока main до опустошения, можно всегда получить последнее консистентное состояние ds. Сейчас у меня реализован этот вариант, но кажется, что он излишне нагружает скрипт, т.к. свечки нужны в потоке main раз в 5 минут, а обновление свечей в потоке коллбэков идёт постоянно.
Не уверен, что у меня есть 100% рабочий рецепт получения консистентных данных из DataSource в потоке main, но можно пытаться делать многократное чтение данных, когда параметры каждой свечи с номером i читаются до тех пор, пока не окажется, что ds:Size() не изменился и ds:T(i), ..., ds:C(i) совпадают с прочитанными ранее, после чего полагаем, что свеча i актуальна и можно переходить к следующей.
В более сложных ситуациях подобного рода проблему полностью решил бы следующий подход, когда есть возможность запуска функции в потоке коллбэков с API типа следующего:
Код
ExecuteCallback(function()
-- обращение к данным ds, которое будет произведено в потоке коллбэков
end)
В языке программирования Java в GUI-приложениях на Swing аналогом является вызов
Код
SwingUtilities.invokeLater(Runnable doRun)
Вопросы/пожелания к разработчикам: 1) рассмотреть возможность введения подобной возможности в терминал; 2) либо отказать, либо реализовать в сжатые сроки.
Главное -- понять, что дневная свеча по фьючерсу на Мосбирже строится с 19:00 (вечерняя торговая сессия) до 18:45 следующего дня, и чем это отличается от терминала (с 10:00 по 23:50 в течение суток).
У меня такая же фигня. Если в этой таблице установить курсор на первую строку, то всё нормально. Если же смотреть на последние строки, то получается как у Вас. Я как-то видео этого безобразия записывал и разработчикам отсылал. Только они ничего поделать не смогли.
Если по теме, то кажется, что с рендерингом этой таблицы истории изменения параметров какие-то реальные проблемы.
Логика ожидания полученных данных из ds (объект datasource, для которого был успешен вызов CreateDataSource), но не более timeout секунд, может быть примерно такая:
Код
local function init(ds, timeout)
local deadline = os.time() + timeout
while os.time() < deadline do
if ds:Size() > 0 then
return true
end
sleep(100)
end
return false
end
Если функция вернула false, значит что-то пошло не так. Таймаут устанавливается пользователем. Я обычно ставлю 15 секунд.
Если не хватило памяти под объекты, значит надо чаще терминал перезагружать. Не может он справиться с большим объёмом данных и/или внутренние ошибки происходят.
Разработчикам пора в дистрибутив терминала включить вот такой bat-файл, чтобы простыню из нескольких пунктов про то, что нужно удалить, не писать.
Код
del /F /Q acnt.dat
del /F /Q alerts.dat
del /F /Q alltrade.dat
del /F /Q banners.dat
del /F /Q classes.dat
del /F /Q firms.dat
del /F /Q limits.dat
del /F /Q locales.dat
del /F /Q orders.dat
del /F /Q par.dat
del /F /Q portfolio.dat
del /F /Q scripts.dat
del /F /Q search.dat
del /F /Q sec.dat
del /F /Q tmsg.dat
del /F /Q tradermsg.dat
del /F /Q trades.dat
del /F /Q trans.dat
del /F /Q transresult.dat
del /F /Q info.log
del /F /Q portfolio.log
info.exe -clear
Ещё (у меня очень редко) бывает, что терминал глючит и тогда корректный lua-код начинает работать некорректно. Ощущение, что при долгой или интенсивной работе терминала изредка происходят какие-то внутренние ошибки, после чего ссылки на некоторые функции и поля таблиц портятся. Чтобы этого избежать, перезапускаю терминал раз в 2-3 дня.
Надеюсь, что в Вашем случае это, всё-таки, ошибка программиста, и можно это исправить. Попробуйте переписать код так, чтобы не было вот таких фрагментов:
Код
value.tbl.bid[indexBid].price
а были примерно такие:
Код
local tbl = value.tbl
local bids = tbl.bid
local price = bids[indexBid].price
Это позволит локализовать ошибку и избавит от многократных обращений к таблицам (см. повторяющиеся фрагменты типа value.tbl.bid, это, к тому же, замедляет код).
Также, если indexBid равен 0 или nil, то стакан частично или полностью пустой.
А зачем Вам "дальний край" стакана value.tbl.bid[indexBid].price ? Обычно value.tbl.bid[1].price более важное значение.
В lua есть 2 функции, pcall и xpcall, для перехвата ошибок, которые могут возникнуть при исполнении кода. Посмотрите в документации к языку lua. Например, здесь http://www.lua.ru/doc/5.1.html даётся описание обеих этих функций.
Я делаю так: если в результате sendTransaction получается результат "", считаем, что транзакция отправлена, будем ждать для неё OnTransReply и OnOrder, иначе рапортуем об ошибке и думаем, что делать дальше (для лимитной заявки слать/не слать новую, для kill-заявки пытаться/не пытаться ещё раз послать kill-заявку).
Когда приходит OnTransReply() / OnOrder(), то разбираемся, что произошло и модифицируем состояние системы. Там логика сложная и в ссылке как-то описана.
Мне сильно помог такой подход: предполагать, что подавляющее большинство транзакций отправлены и обработаны без проблем, а там, где возникли какие-то ошибки, работают "заплатки", разбирающиеся с частными патологиями. Тут всё (OnOrder(), OnTrade() и OnTransReply()) надо использовать.
Реально в коде используется проверка статусов 3, 4, 5 и 13.
nero333 написал: _sk_ , а сможете ли вы запустить скрипт из другого потока и, если да, то каким образом?
Скрипт -- это некий код, часть из которого исполняется в main-потоке, а часть в потоке коллбэков. Запускается скрипт кнопкой "Запустить". После этого начинает выполняться код main() и время от времени будут вызываться коллбэки. Фраза "запустить скрипт из другого потока" кажется некорректной.
финамовец, Вам уже пора в командировку съездить в Новосибирск к разработчикам ARQA вместе со своим компьютером. Как вариант -- дать разработчикам доступ через TeamViewer, чтобы они сами всё видели, что Вы видите своими глазами.
nero333 написал: _sk_, спасибо, это хорошая идея, думаю реализовать что-то подобное. Вопрос был к разработчикам относительно кнопки "Остановить", аварийный выход - это всё-таки когда скрипт падает с выводом ошибки в поле "Ошибки выполнения скрипта".
Кнопка "Остановить" это не аварийный выход, а просто остановка скрипта. Принудительная (в Вашем термине "аварийная") остановка происходит не по нажатию кнопки, а по истечении таймаута 5 сек. И это не зависит от того что в это время делает main, что-бы Вы туда не написали скрипт принудительно завершится. Таймаут можно изменить, это делается в return события OnStop (см документацию QLUA.chm) Если Вам что-то нужно снять заявки перед остановкой скрипта, делайте это в самом OnStop
OnStop -- это последний коллбэк, который будет вызван при остановке скрипта. Однако, чтобы корректно сделать всё, что необходимо для снятия отправленных заявок в общем случае, не получится. Например, если транзакция на постановку заявки отправлена, OnTransReply ещё не пришёл, а пришёл OnStop. Как снять заявку, если мы ещё не знаем и не узнаем её orderNum? Никак.
Кроме того, в OnStop делать завершение неудобно, т.к. логика обработки находится в main-потоке.
Именно поэтому нормальная практика прерывания потока в многопоточном программировании состоит в уведомлении другого потока о том, что надо завершить работу. Эта практика и была предложена.
nero333 написал: Он не остановит работу, ведь main продолжает работать. Что вы посоветуете сделать, как выйти из main-a только удостоверившись, что все заявки сняты (а это можно сделать только по результатам колбека OnOrder)?
У меня используется следующая схема. Скрипт создаёт окно, где отображается информация о его работе (позиции, прибыли/убытки и др.). Если его закрывают нажатием на крестик в правом верхнем углу окна, то скрипт понимает, что окно закрыто (IsWindowClosed(windowId) == true), поднимает флаг, аналогичный Вашему IS_STOPPING, снимает необходимые заявки, закрывает файлы, в которые шла запись и т.п., после чего завершает исполнение функции main. Получается, что использование крестика в правом верхнем углу окна -- это штатный выход из скрипта, а нажатие кнопки "Остановить" в окне скриптов -- аварийный выход.
Когда ещё был старый форум, там это обсуждалось. Пусть кто-нибудь из представителей ARQA дальше по теме видимости изменения переменных отвечает (даёт ссылку на соответствующее обсуждение или заново тут ответит).
Приведённый мною код работает с конца 2013 года без каких-либо проблем.
Цитата
Моя блокировка отличается от такой блокировке только тем, что разблокировка происходит по уведомлению из другого треда напрямую непосредственно в момент, когда нужно произвести действия.
Конечно, так и надо делать. Только в стандартном Lua для этого нет средств, а в QLua, наверное, через sinsert как-то можно извратиться и реализовать Lock. На практике же повелось так, что в main-потоке большинство пишет цикл, совершающий нужные проверки/действия и засыпающий, если это надо/есть возможность.
В коде выше чтение и запись в submit/get не синхронизированы. Что случится, если вызов будет произведен одновременно на двух родных потоках (мейн и колбек)? Например, колбек вызовет submit, который уже модифицирует очередь при присвеоении, но еще не завершит операцию, а get уже начнет из нее читать?
submit меняет только tail, get меняет только head. И указатели двигаются только в те моменты, когда структура готова. За видимость переменных из разных потоков разработчики QLua уже подумали.
Цитата
Все же, хотелось бы избежать копирования всего и вся из коллбеков в очередь.
Передавайте только те данные, которые нужны. Полагаю, что если использовать что-то блокирующее, у Вас производительность понизится.
Как эвристику можно использовать следующее наблюдение: если при чтении из очереди она оказалась пуста, можно поставить sleep с аргументом побольше, если же непуста, то вообще не вызывать sleep.
Если очередь разгребается быстрее, чем пополняется, то в памяти растёт только массив, на базе которого реализована очередь. Этот массив занимает в памяти относительно небольшой размер, а перезапуск скрипта, который происходит раз в несколько дней, приводит к старту с малого размера массива.
Наблюдаю проблему в терминале 7.14.1.7 уже второй раз.
Строим графики цен нескольких инструментов в одном окне на низком таймфрейме (5 мин, например). Накладываем уровни Фибоначчи на график одной из цен. Ждём некоторое время, пока появляются новые свечи и график не сдвинется так, что уже не видно тех точек, по которым уровни Фибоначчи были построен. У меня обычно это наступает на следующий день после построения уровней. Возможно, надо чтобы был рестарт сервера, когда графики исчезают, а потом снова появляются.
В результате видим вот такие два графика. Первый -- это если проскроллировать график цен в прошлое, чтобы были видны точки, по которым строились уровни, а второй -- если проскроллировать график цен обратно. Когда большая свеча уходит из окна, график цен меняет свой масштаб по вертикали и при этом уровни уносит чёрт знает куда.
Один из вариантов кардинально решить проблему многопоточности при программировании на QLua описан ниже.
В потоке main делаем всю логику торгового робота, а из потока коллбэков только передаём данные в поток main через очередь. Очередь решает задачу синхронизации, коллбэки завершают свою работу максимально быстро, не тормозя UI терминала.
Реализация очереди функций на базе массива позволяет в потоке коллбэков указать, какие данные нужно использовать, и как именно. При получении функции в потоке main остаётся лишь запустить её.
Код
--
-- Реализация очереди из функций и их исполнения.
--
local Executor = {}
--- Конструктор.
-- @param self объект
local function new(self)
local queue = {
head = 1,
tail = 0
}
setmetatable(queue, self)
self.__index = self
return queue
end
Executor.new = new
--- Поместить функцию в очередь на выполнение.
-- @param self объект
-- @param f функция
local function submit(self, f)
if type(f) == "function" then
self[self.tail + 1] = f
self.tail = self.tail + 1
end
end
Executor.submit = submit
--- Получить очередную функцию из очереди.
-- @param self объект
-- @return функция
local function get(self)
if self.head > self.tail then
return nil
else
local f = self[self.head]
self[self.head] = nil
self.head = self.head + 1
return f
end
end
Executor.get = get
--- Выполнять функции из очереди либо пока они там есть, либо пока не будет выполнено указанное количество функций.
-- @param self объект
-- @param max максимальное количество исполняемых за один раз функций
local function execute(self, max)
max = max or 1000000
while max > 0 do
local f = self:get()
if f == nil then
return
else
f()
max = max - 1
end
end
end
Executor.execute = execute
return Executor
Используется это примерно так.
Код
local tradeCounters = {} -- таблица[secCode], содержащая количество обезличенных сделок по каждому инструменту
В потоке коллбэков пишем, например:
Код
function OnAllTrade(t)
executor:submit(function()
tradeCounters[t.sec_code] = (tradeCounters[t.sec_code] or 0) + 1
end)
end
В потоке main пишем цикл, достаточно часто вызывающий функцию execute, т.е. что-то типа:
Код
while not interrupted do
....
executor:execute()
....
end
В результате в потоке main выполнится функция, которую мы задали в потоке коллбэков с данными, которые в тот момент были доступны.
Не знаю, у меня в 7.14.1.7 для SRZ7 (сбер) совпадает с рассчитанной и с сайтом = 0.59.
По SRZ7 у меня тоже совпадает. Я писал про SiZ7.
В отчёте от биржи за вчерашний день комиссия за сделку по SiZ7 указана 0.82 руб. В терминале (создать окно -> сделки -> редактировать таблицу + добавить столбцы про комиссию) видим 0.91 руб с утра и 0.92 руб во второй половине дня. В торговых роботах из коллбэков OnTrade приходит комиссия 0.91/0.92 руб.
Настоятельная просьба к разработчикам терминала оперативно пояснить, какая комиссия по SiZ7 правильная? Если проблема в терминале/сервере -- укажите. Если проблема в том, что биржа так транслирует данные -- тоже укажите.
Сейчас терминал (7.12) транслирует комиссию по SiZ7 равную 0.91 руб/контракт, а на сайте биржи указано, что должна быть 0.82 руб/контракт. Где правда? Это биржа так транслирует данные?
Очень хочется, чтобы хоть у кого-то получилось понять, как гарантированно воспроизвести проблемы, подобные описанным выше. У меня не получилось, хотя я даже некий скрипт специально писал и отправлял разработчикам для тестов. Пока не будет чёткого алгоритма воспроизведения проблемы разработчики нам вряд ли помогут, к сожалению.
Отключение тиковых графиков проблему не решает, а вот отключение трансляции обезличенных сделок -- по первым ощущения ее устраняет. Мне они категорически нужны, не понимаю что делать.
Похоже, что есть некоторые ошибки в терминале, которые проявляются только под нагрузкой (и, может быть, только в некоторые дни, скажем, связанные с экспирацией или ещё какими-то событиями). Трансляция обезличенных сделок таковой является.
Для минимизации вероятности проблем можно перезапускать терминал каждый день или через день.
Конечно, хотелось бы, чтобы разработчики пофиксили причину, поскольку регулярный перезапуск терминала делать неудобно.
Если посмотреть внимательно на вопрос, то станет ясно, что он был об обезличенных сделках (OnAllTrade), а не о сделках пользователя терминала (OnTrade).
Не знаю, что надо делать с мордами невнимательных комментаторов.
Отдельные биты, как я понимаю, были сделаны разработчиками терминала из-за того, что в обезличенных сделках, представляющих собой данные по индексам, оба бита нулевые.
local SELL_FLAG = 1
local BUY_FLAG = 2
function OnAllTrade(allTrade)
...
local buySell = 0
if bit.band(currTrade.flags, BUY_FLAG) == BUY_FLAG then
buySell = 1
elseif bit.band(currTrade.flags, SELL_FLAG) == SELL_FLAG then
buySell = -1
end
...
end
Стандартная схема работы виртуальных машин: 1) выделяем какой-то объём памяти для работы; 2) работаем, периодически вызывая сборщик мусора (мелкая гребёнка); 3) если после сборки мусора осталось мало свободной памяти, выделяем больше памяти (периодическое повышение объёма) и продолжаем работать с пункта 2). Продвинутые виртуальные машины умеют уменьшать объём выделенной памяти, если потребность в ней снизилась. Похоже, что виртуальная машина lua к таким не относится.
Удаление элементов в больших таблицах., Крайне медленная работа table.remove и возможные обходные пути для быстрого удаления большого числа элементов крупных массивов/таблиц.
В такой структуре данных легко добавлять в конец, удалять из начала и итерировать по элементам. Если в буфере кончилось свободное место, можно увеличить его размер, скажем, вдвое.