_sk_
_sk_ личный блог
15 мая 2020, 16:29

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

В новой версии терминала 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.
23 Комментария
  • 3Qu
    15 мая 2020, 16:52
    Не понял, зачем 
    return Arrays
    и куда мы его возвращаем?
    Вроде, поместили функции в массив, и пользуйся на здоровье.

      • 3Qu
        15 мая 2020, 17:11
        _sk_, я бы не стал. С пространствами имен проще работать, за именами следить не надо. Но это дело вкуса.
  • Андрей Иванов
    15 мая 2020, 17:25
    у меня дак все простенько, где надо убрать ноль использую math.floor и все:)
    как у Вас терминал 8.5.2 не падал? у меня дак рекорд 7 дней и сегодня упал без дампа( но стал заметно стабильнее чем 8.5.1 тот совсем только два дня держался
      • Андрей Иванов
        15 мая 2020, 17:43
        _sk_, очень хорошо что у Вас не падал) а я дак уже думаю над автоматизацией перезапуска квика и запуском скриптов, если упадет. До 8 июня осталось совсем чуть чуть.
      • Rostislav Kudryashov
        15 мая 2020, 17:38
        _sk_, кто тебе сказал, что «math.floor для нецелых чисел нельзя применять»?
        А вот для целых чисел применение math.floor бессмысленно!
  • Rostislav Kudryashov
    15 мая 2020, 17:36
    Если иметь дело с положительными числами, отбросить дробную часть x проще всего, написав x — x % 1.
    Отрицательное число это округлит до ближайшего меньшего целого.
  • Rostislav Kudryashov
    15 мая 2020, 18:01
    Может, подойдёт math.modf ???
      • Rostislav Kudryashov
        15 мая 2020, 18:19
        _sk_, не подскажешь, где  скачать бесплатно «Programming in Lua. Fourth Ed» со встроенным древовидным оглавлением как в Third Ed?
      • Rostislav Kudryashov
        15 мая 2020, 18:22
        _sk_, не надо адаптировать! modf выдаёт целочисленный результат без адаптации. Заведи се6е автономный lua53 и  запустив start lua53.exe сможешь получить
        > = math.modf (-1.2345)
        -1         -0.2345
  • s_mike@rambler.ru
    15 мая 2020, 18:29
    Чтобы вернуть строковое представление чисел в том же виде, что и в луа 5.1, достаточно просто переписать функцию tostring и ничего больше не править в текстах. Что-то типа следующего

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

    Return tostring__(x)
    End
    • rishat1
      05 июля 2022, 10:32
      s_mike@rambler.ru, спасибо!
  • s_mike@rambler.ru
    15 мая 2020, 19:00
    Заодно тогда… функцию isempty можно выкинуть, ee полностью заменяет станлартная next().

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

    Также там есть теперь и table.copy… Одним словом, выкинуть можно весь этот модуль полностью)))))
  • П М
    15 мая 2020, 20:02

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

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

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

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн