Блог им. _sk_

Опыт доработки QLua-скриптов для QUIK 8.5.2

    • 15 мая 2020, 16:29
    • |
    • _sk_
  • Еще
В новой версии терминала QUIK 8.5.2 произведён апгрейд языка Lua для написания торговых скриптов с версии 5.1 до версии 5.3. Это нужно для того, чтобы корректно обрабатывать 19-значные номера заявок и сделок на срочном рынке МосБиржи. Типа number в Lua 5.1 не подходит: там все числа хранятся как double, соответственно целые числа до 2^53 = 9 007 199 254 740 992 записываются без потери точности, а 19-значные номера заявок и сделок будут больше этой границы.

Версия Lua 5.3 обратно несовместима с Lua 5.1. Я почти не использовал внешние библиотеки и для меня было два важных изменения: отказ от module (это было сделано в версии 5.2) и введение целочисленной арифметики (версия 5.3).

Для избавления от использования module пришлось переработать много кода, хотя изменения были несложные. Приведу пример. Раньше был такой код Arrays.lua для работы с массивами:

--
-- Выполнение действий с массивами.
--

local pairs = pairs
local type = type

module(...)

--- Создать копию массива (таблицы)
-- @return копию массива (таблицы)
function copy(array)
    local copy_array = {}
    if type(array) ~= "table" then
        return array
    end
    for k, v in pairs(array) do
        if type(v) == "table" then
            copy_array[k] = copy(v)
        else
            copy_array[k] = v
        end
    end
    return copy_array
end

--- Узнать, начинается ли индексация в массиве с нуля или с единицы.
-- @return 0 или 1
function base(array)
    if array[0] ~= nil then
        return 0
    else
        return 1
    end
end

--- Вычислить число элементов в массиве.
-- @return число элементов в массиве
function size(array)
    local n = 0
    for _, _ in pairs(array) do
        n = n + 1
    end
    return n
end

--- Проверить пустой или нет массив.
-- @return true/false
function isEmpty(array)
    for _, _ in pairs(array) do
        return false
    end
    return true
end

--- Получить первый индекс массива, где ничего не записано. Поиск начинается с 1.
-- @return первый индекс массива, где ничего не записано
function firstEmptyIndex(array)
    local i = 1
    while array[i] ~= nil do
        i = i + 1
    end
    return i
end

Для переработки надо:
1) удалить module и запоминание функций до него;
2) завести таблицу с функциями, которые были в модуле;
3) вернуть эту таблицу с помощью return.
В результате код становится таким:

--
-- Выполнение действий с массивами.
--

local Arrays = {}

--- Создать копию массива (таблицы)
-- @return копия массива (таблицы)
local function copy(array)
    local copy_array = {}
    if type(array) ~= "table" then
        return array
    end
    for k, v in pairs(array) do
        if type(v) == "table" then
            copy_array[k] = copy(v)
        else
            copy_array[k] = v
        end
    end
    return copy_array
end

Arrays.copy = copy

--- Узнать, начинается ли индексация в массиве с нуля или с единицы.
-- @return 0 или 1
local function base(array)
    if array[0] ~= nil then
        return 0
    else
        return 1
    end
end

Arrays.base = base

--- Вычислить число элементов в массиве.
-- @return число элементов в массиве
local function size(array)
    local n = 0
    for _, _ in pairs(array) do
        n = n + 1
    end
    return n
end

Arrays.size = size

--- Проверить пустой или нет массив.
-- @return true/false
local function isEmpty(array)
    return next(array) == nil
end

Arrays.isEmpty = isEmpty

--- Получить первый индекс массива, где ничего не записано. Поиск начинается с 1.
-- @return первый индекс массива, где ничего не записано
local function firstEmptyIndex(array)
    local i = 1
    while array[i] ~= nil do
        i = i + 1
    end
    return i
end

Arrays.firstEmptyIndex = firstEmptyIndex

return Arrays
Второе важное изменение касается целочисленной арифметики. В Lua 5.3 есть 2 типа чисел: integer и float. Для первых реализована целочисленная арифметика, вторые нужны для представления вещественных чисел. Можно считать, что в Lua 5.1 были только float-числа. Номера заявок и сделок теперь будут представляться типом integer, и там хватит места для 19-значных чисел.

В терминале QUIK все торговые данные про цены сделок, их объёмы и т.п. приходят в виде float. Раньше, до версии 8.5.2, если количество акций в сделке или цена оказывались целым числом, то в случае преобразования в строку функцией tostring() результат не имел десятичной точки. Сейчас же она появляется всегда. Например, для цены RI 109120 в старых версиях терминала получается строка «109120», а в новых — «109120.0»; три фьючерса в сделке вместо строки «3» дают «3.0».

В принципе, это не сильно мешает, но во многих местах (логах, сообщениях, создаваемых скриптами таблицах с рыночной информацией и т.п.) вылезаются эти лишние ".0" в конце. Хуже обстоит дело в том месте, где отправляется транзакция на постановку заявки. Если в новой версии терминала указать в поле QUANTITY вместо «3» значение «3.0», то sendTransaction не отправит транзакцию и выдаст ошибку:
«Сообщение об ошибке: Число не может содержать знак разделителя дробной части».

Выйти из положения можно следующим образом, который подойдёт для Lua 5.3 и 5.1 сразу. Определим функцию tryInt, которая пытается преобразовать float-число в integer, если это возможно:

local tointeger = math.tointeger or (function(x) return x end)

--- Сделать вещественное число целым, если это возможно.
-- @param x вещественное число
-- @return целое число или исходное вещественное число, если преобразование невозможно
function tryInt(x)
    return tointeger(x) or x
end
Теперь ищем все места, где заполняется таблица с параметрами транзакции для постановки заявки, и там вместо обычного
QUANTITY = tostring(выражение),
пишем
QUANTITY = tostring(tryInt(выражение)),
В принципе, этого должно быть достаточно для правильной работы скриптов.

Если же хочется сделать, чтобы не вылезали «лишние хвосты» вида ".0" в других местах, можно поставить аналогичные «ловушки» для выделения целочисленных значений в местах, где они приходят в QLua-код. У меня это происходит в функциях обратного вызова OnOrder, OnTrade и OnTransReply.

В функции OnOrder я преобразую с помощью функции tryInt() поля balance и qty, в функции OnTrade — поля order_qty и qty, в функции OnTransReply — поле quantity. Поля с ценой price решил не трогать: пусть всегда имеют тип float.

Возможно, в вашем коде будут ещё какие-то изменения.

Если есть желание помочь и передать свой опыт, — напишите в комментариях, как вы добивались работоспособности QLua-кода в терминале QUIK 8.5.2.
  • обсудить на форуме:
  • Quik Lua
★41
23 комментария
Не понял, зачем 
return Arrays
и куда мы его возвращаем?
Вроде, поместили функции в массив, и пользуйся на здоровье.

avatar
3Qu, потом используем в другом файле через
local Arrays = require(«util.Arrays»)
avatar
_sk_, я бы не стал. С пространствами имен проще работать, за именами следить не надо. Но это дело вкуса.
avatar
у меня дак все простенько, где надо убрать ноль использую math.floor и все:)
как у Вас терминал 8.5.2 не падал? у меня дак рекорд 7 дней и сегодня упал без дампа( но стал заметно стабильнее чем 8.5.1 тот совсем только два дня держался
Андрей Иванов, терминал 8.5.1 после запуска скриптов крашился через одну-две секунды без дампов. Совсем не работоспособная версия была. Версия 8.5.2 сильно лучше, ещё не падала, но есть кое-какие недоделки. На форуме forum.quik.ru про это можно долго читать.
avatar
_sk_, очень хорошо что у Вас не падал) а я дак уже думаю над автоматизацией перезапуска квика и запуском скриптов, если упадет. До 8 июня осталось совсем чуть чуть.
Андрей Иванов, где-то читал слухи, что возможен ещё один перенос срока релиза на бирже на более поздний срок. Чтобы терминал не падал, я раз в неделю (в понедельник с утра) его перезагружаю с перезаказом всех данных.
avatar
_sk_, Хорошо бы если перенесут.
Андрей Иванов, math.floor для нецелых чисел нельзя применять. Я хотел какой-то более мягкий способ склонить Lua к использованию целых чисел. Получилось — хорошо, не получилось — будем с дробными работать.
avatar
_sk_, кто тебе сказал, что «math.floor для нецелых чисел нельзя применять»?
А вот для целых чисел применение math.floor бессмысленно!
Rostislav Kudryashov, исходная задача был такая: если число представимо как целое, перевести его в целое, иначе оставить как было. Функцию math.floor(), конечно, можно применять для получения целого числа из дробного. В комментарии выше я не совсем точно выразился.
avatar
Если иметь дело с положительными числами, отбросить дробную часть x проще всего, написав x — x % 1.
Отрицательное число это округлит до ближайшего меньшего целого.
Rostislav Kudryashov, можно по всякому. Универсальные способы, конечно, предпочтительнее.
avatar
Может, подойдёт math.modf ???
Rostislav Kudryashov, и эту функцию можно адаптировать под задачу сделать число типом integer.
avatar
_sk_, не подскажешь, где  скачать бесплатно «Programming in Lua. Fourth Ed» со встроенным древовидным оглавлением как в Third Ed?
Rostislav Kudryashov, не подскажу. В самом начале я читал книгу автора языка Lua, а сейчас референсом пользуюсь
www.lua.org/manual/5.3/
avatar
_sk_, не надо адаптировать! modf выдаёт целочисленный результат без адаптации. Заведи се6е автономный lua53 и  запустив start lua53.exe сможешь получить
> = math.modf (-1.2345)
-1         -0.2345
Чтобы вернуть строковое представление чисел в том же виде, что и в луа 5.1, достаточно просто переписать функцию tostring и ничего больше не править в текстах. Что-то типа следующего

local tostring__ = tostring
Function tostring(x)
If math.tointeger(x) then
Return string.format("%0i",x)
End

Return tostring__(x)
End
s_mike@rambler.ru, хороший вариант!
avatar
s_mike@rambler.ru, спасибо!
avatar
Заодно тогда… функцию isempty можно выкинуть, ee полностью заменяет станлартная next().

Ну и размер массива можно вычислить встроенной функцией, а не пользовательской. Функция появилась в 5.3 в пространстве table

Также там есть теперь и table.copy… Одним словом, выкинуть можно весь этот модуль полностью)))))

что-то у меня скомпиленный индикатор не хочет запускать
даже после перекомпиляции под lua 5.3.5

а без компиляции норм

upd: разобрался, надо использовать x64 luac, а я случайно собрал x86 luac из исходников

avatar

теги блога _sk_

....все тэги



UPDONW
Новый дизайн