Блог им. morefinances

Qlua: структура скрипта для торгового терминала, обработка обрыва связи и её возобновления, работа с файлами

Сегодня начинаем уже писать полноценные скрипты для терминала, а не отдельные блоки кода на lua.

Пройдем:

  • Структуру типового скрипта qlua с примерами.
  • Обработку скриптом «обрыва связи» с сервером и возобновления работы.
  • Работу с файлами: запись, перезапись и чтение файла.
  • getScriptPath, getWorkingFolder

Структура скрипта

В торговом терминале можно запускать небольшие примеры на lua, как мы это делали ранее, но если говорить о постоянно работающем алгоритме, а не о компактной программе, которая должна выполнить только несколько коротких действий, то минимальная структура скрипта для квика будет содержать следующие функции:

Qlua: структура скрипта для торгового терминала, обработка обрыва связи и её возобновления, работа с файлами

function OnInit – инициализирует глобальные переменные и константы (например, торгуемые бумаги, размеры тейка и стопа, торговый счет и пр.), имена таблиц, необходимых файлов.

function OnStop – функция остановки скрипта, активируется при нажатии клавиши «Остановить» в панели скриптов терминала.

function main – основная функция, создает отдельный поток для выполнения скрипта. Обычно внутри main создается цикл для непрерывной работы, т.к. без него функция выполнит один раз весь код, который в ней прописан и скрипт остановится.

Например:

function OnInit()
  progname="example_one: "
end
 

function main()
  message(progname.." старт")
end

Данный код только выдаст сообщение и остановит скрипт.

Поэтому создаем переменную, которая пока будет true, цикл в main будет работать:

function OnInit()
  progname="example_one: "
  do_it = true
end

function OnStop()
  do_it = false
  message(progname.." завершение работы")
end

function main()
  message(progname.." старт")
    while do_it do
     sleep(1000)
    end
end

Не забываем ставить паузу sleep внутри цикла, чтобы не перегружать процессор.

Теперь скрипт не выключается, а при нажатии «Остановить» он выдаст сообщение о завершении работы и приостановит работу.

Внутри функции main до цикла while можно поставить ту часть кода, которую необходимо запустить всего 1 раз, а весь основной код нашего алгоритма уже размещать внутри цикла. Дополнительно мы можем прописывать собственные функции, которые будем задействовать до и внутри while, в этом случае их необходимо разместить выше функции main.

Напишем скрипт по которому сравним данные от os.sysdate и getInfoParam о которых говорили в прошлый раз:

function OnInit()
   progname="example_two: "
   do_it = true
end

function OnStop()
   do_it = false
   message(progname.." завершение работы")
end

function main()
   message(progname.." старт")
   for i=1, 20 do
     time1 = os.sysdate()
     time2 = getInfoParam('SERVERTIME')
     time_txt=time1.hour..":"..time1.min..":"..time1.sec
     message("Время через os.sysdate: "..time_txt.." время SERVERTIME:"..time2)
     sleep(250)
   end
   while do_it do
     sleep(1000)
   end

end

Цикл for, в котором запрашиваем время, ставим до while, чтобы программа выполнила его всего 1 раз, иначе он будет постоянно перезапускаться.

Если мы запустим скрипт, то увидим 20 выводов с описанием времени, полученных разным способом. Как правило, в отдельных строках мы будем наблюдать расхождение о которых говорили ранее.

Qlua: структура скрипта для торгового терминала, обработка обрыва связи и её возобновления, работа с файлами 

Если бы мы хотели, чтобы вывод был только по тем строкам, где время расходится, то можно добавить небольшое условие, по секундам:

sec1 = time1.sec -- секунды, полученные от sysdate

sec2 = tonumber(string.sub(time2, #time2-1, #time2)) -- вырезаем секунды (последние 2 символа строки), полученные от SERVERTIME, переводим в число

А вывод поставим в условие:

if sec1~=sec2 then … end

Если мы захотим, чтобы после вывода строк скрипт сам остановился, то после цикла for достаточно приравнять переменную do_it = false. Итого получаем:

function OnInit()
   progname="example_three: "
   do_it = true
end

function OnStop()
   do_it = false
   message(progname.." завершение работы")
end

function main()
   message(progname.." старт")
   for i=1, 20 do
     time1 = os.sysdate()
     time2 = getInfoParam('SERVERTIME')
     sec1 = time1.sec
     sec2 = tonumber(string.sub(time2, #time2-1, #time2))
     if sec1~=sec2 then
        time_txt=time1.hour..":"..time1.min..":"..time1.sec
       message("Время через os.sysdate: "..time_txt.." время SERVERTIME:"..time2)
     end

     sleep(250)

    end

   do_it = false
   while do_it do
     sleep(1000)
   end

end

Теперь скрипт выводит только те строки, где были выявлено расхождение.

Qlua: структура скрипта для торгового терминала, обработка обрыва связи и её возобновления, работа с файлами

При этом после завершения скрипта информация об этом не вывелась, т.к. мы не нажимали кнопку «Остановить». Однако, если бы мы вместо do_it = false написали бы OnStop(), то и скрипт бы остановился, и надпись появилась, т.к. вызвалась в этом случае соответствующая функция.



Рассмотрим еще небольшой пример, в котором коснемся работы сервера.

Иногда связь с сервером брокера может разрываться и важно, чтобы скрипт правильно обрабатывал это событие, и самое главное: нормально возвращался к работе после автоматического восстановления работы.

С помощью функции isConnected() можно получить текущий статус соединения:

1 – если связь установлена и 0 – если связи нет.

function OnInit()
  do_it = true
  progname="example_four: "
end

function OnStop()
  do_it =  false
  message(progname.." завершение работы")
end

function main()
  message(progname.." старт")
  connect = isConnected()
  if connect == 1 then
     message(progname..'связь с сервером установлена')
  else
     message(progname..'связи с сервером нет')
  end
  while do_it do
    sleep(1000)
  end
end

Теперь при запуске скрипта в терминале выйдет запись есть ли связь сервером или нет. Но это только на старте.

Чтобы обрабатывать само событие обрыва соединения нужно воспользоваться функцией обратного вызова OnDisconnected(), которая вызывается терминалом при данном событии. Для этого разместим эту функцию до main и опишем сообщение о том, что терминал отключен от сервера. Здесь же в скрипте можно добавить переменную status, которая будет равна 1, если связь есть и -1, если нет. Тогда в этой функции OnDisconnected() мы также поменяем ей знак, а уже в цикле main`а обработаем это событие:

function OnInit()
  do_it = true
  progname="example_five: "
  status = 0
  mes_ind = 0
end

function OnStop()
  do_it = false
  message(progname.."завершение работы")
end

function OnDisconnected()
  status = - 1
  message(progname..'терминал отключен от сервера')
end

function main()
  message(progname.." старт")
  connect = isConnected()
  if connect == 1 then
     message(progname..' связь с сервером установлена')
     status = 1
  else
     message(progname..'связи с сервером нет')
     status = - 1
  end

  while do_it do
    connect2 = isConnected()          
    if status == -1 and connect2 == 1 then
                                message(progname..'соединение с сервером восстановлено')
      status = 1
    end
    sleep(1000)
  end
end

В функциях обратного вызова лучше не включать большое количество кода, т.к. это может сильно подтормаживать терминал, в отдельных моментах приводить к его зависанию.

Теперь можно запустить скрипт при установленном соединении, а после выключить и повторно включить. Скрипт просигналит про каждое изменение.

Аналогично можно запустить скрипт при выключенной связи и после старта подключиться. Скрипт укажет и на отсутствие связи на старте и на восстановление соединения после.


Работа с файлами


Запись данных в файл

Запись осуществляется следующим образом:

filename = "save_one" -- имя файла
DirectionSaveFile=tostring("C:\\files\\"..filename..".csv")  -- директория сохранения и формат
my_csv=io.open(DirectionSaveFile,"w")  -- создание файла
abc = "Проба записи в файл" -- переменная для записи
my_csv:write(abc..";\n") -- запись в файл, в конце указываем разделитель и перенос строки
my_csv:flush() 
my_csv:close() -- закрытие файла

Чтобы скрипт корректно отработал необходимо на C: создать папку files. Как видно в переменную DirectionSaveFile путь указывается с помощью двойного обратного слэша \\, т.к. в строке он переводится в одинарный. Это можно увидеть, если добавить message(DirectionSaveFile) в конце скрипта, например.


Перезапись

Если в io.open(DirectionSaveFile,«w»)  поставить вместо «w» параметр «a+», то файл будет создан, если его нет, или открыт если уже есть для дополнения, т.е. сохранятся предыдущие записи в файле. Это удобно для записи логов, например.

filename = "save_one"
DirectionSaveFile=tostring("C:\\files\\"..filename..".csv")
my_csv=io.open(DirectionSaveFile,"a+")

for i = 1, 10 do
  my_csv:write(i..";\n")
end

my_csv:flush() 
my_csv:close()

Если у вас выскакивает ошибка, то, возможно, вы ранее открыли созданный файл save_one.csv в Excel и не закрыли перед повторным запуском. Терминал не сможет сохранить в этом случае данные в открытый в программе файл и будет сообщать об этом в панели скриптов.

Чтение

В случае чтения файла используется параметр «r» в io.open:

DirectionFile=tostring("C:\\files\\save_one.csv")
f = io.open(DirectionFile, "r");
t = f:read("*all")
f:close()

В этом случае все данные файла будут записаны в одну строку t.

Чтобы разбить её в массив по разделителю (в нашем варианте ";") необходимо добавить цикл:

s = {} -- создаем массив
a = 1 -- счетчик индекса массива
for i in string.gmatch(t, "[^;]+") do -- разбиваем строку t по ";" и итерируем
   s[a] = tonumber(i)
   message(tostring(s[a])) -- выводим на экран полученный элемент массива
   a = a + 1
end

Последнему значению будет присвоено nil – т.к. это конец файла. При этом размер полученного массива message(""..#s) покажет правильный размер массива без nil.

Если вы сохраняли данные в файл в одну строку без разделителей, то итерировать не нужно, просто делаются необходимые срезы строки через string.sub с переводом подстроки в число (tonumber) для корректной работы в дальнейшем.

Альтернативный код построчного чтения файла через io.lines:

DirectionFile=tostring("C:\\files\\save_one.csv")
t = {}
for line in io.lines(DirectionFile) do
  t[#t+1]=line
  message(t[#t])
end

Работа с файлами нам позже пригодится и для записи логов, и для взаимодействия с другими скриптами, в т.ч. на любых других языках.

 

Полезные функции при работе с файлами:

getScriptPath выдаст нам путь, по которому находится запускаемый скрипт.

getWorkingFolder вернёт путь нахождения терминала (файл info.exe). Особенно полезно, когда используется несколько квиков от разных брокеров.

scriptpath=getScriptPath()

workfold = getWorkingFolder()

message(scriptpath)

message(workfold) 

И в том, и в другом случае функции возвращают адрес без слеша в конце строки (т.е. в дальнейшем нужно будет прибавить… "\\").

 

Все файлы сегодняшних примеров размещены для удобства на github:

https://github.com/morefinances/qlua

 

Упражнения для закрепления:

1. Сохраните в файл текущее время и дату в формате: ГГГГММЧЧММСС (для 12:05 14.07.23 это 202307141205). Помните, что нужно добавлять нули, чтобы был корректный формат (подробнее об этом говорили в прошлой статье).

2. Сгенерируйте циклом последовательность Фибоначчи (до 100-го значения) и сохраните его в файл в одну строку и в другой файл с разбивкой по 1 элементу в строке.

3. В прошлый раз в качестве одного из упражнения было необходимо сделать массив параметров getInfoParam и пройти по данной функции циклом с выводом результатов каждого запроса. Попробуйте теперь эти результаты сохранить в файл и другим скриптом прочитать их.

 

В предыдущих сериях:

Qlua: введение
https://smart-lab.ru/blog/917696.php


Настраиваем торговый терминал и редактор кода
https://smart-lab.ru/blog/918869.php  


Основы qlua, часть 1:

message, конкатенация
фильтрация по сообщениям в терминале
PrintDbgStr, комментарии
типы данных, type
операции с числами
операции со строками
операции с таблицами
условные операторы
https://smart-lab.ru/blog/920031.php 

Основы qlua, часть 2:
Циклы for … do … end, while do … end, repeat … until.
sleep, break, goto.
как пройти весь массив циклом, как пройти таблицу по ключам
локальные и глобальные переменные, функции
получение даты и времени
получение данных через getInfoParam
https://smart-lab.ru/blog/921366.php


Теги: qlua для начинающих, кружок авиамоделизма.

  • обсудить на форуме:
  • Quik Lua
★27
13 комментариев
про кружок авиамоделизма что за пасхалка такая?
avatar
wrmngr, в самое первой статье (введение) об этом.
avatar
Каждого, кто собирается программировать на QLua, должен сильно волновать ответ на следующий вопрос: стоит ли изучать Lua или воспользоваться некоей библиотекой (как пример QuikSharp + QuikPy) и в дальнейшем писать свои программы на другом ( скажем мягко — менее экзотическом) языке (Python, C#, С++ и т.п.).
Когда разработчики Квика встраивали Луа в  Квик, это был (Луа 5.1 х32) популярный язык с огромным количеством всяких разных библиотек, компилятором (LuaJIT), своим менеджером пакетов и большим комьюнити. Потом прошло некоторое количество времени, Луа в Квике изменился на Луа 5.4 х64 и все достоинства его превратились в тыкву. Оказалось, что это практически умирающий ( утопающий) язык, поддержка которого — дело рук самих утопающих. Кто-то успел ухватить тренд и быстренько переписал все на Python и остался конкурентноспособен ( хороший пример Torch -> PyTorch ), кто-то все проспал и утонул (ну или еще пока барахтается).
В связи с выше сказанным, очень хотелось бы выяснить, так сказать на берегу, стоит ли свеч QLua, или можно построить такого же стабильного и быстрого робота на другом языке.
avatar
Synthetic, у вас странные представления о программировании. Если вам для использования языка необходимо комьюнити и популярность, может стоит податься в диджеи ?

Стоит ли код, управляющий деньгами, отдалять от биржи на еще одну прокладку, чтобы быть модным и в тренде?

Что экзотического может быть в минималистическом скрипте с паскалеподобным синтаксисом, спецификация которого осваивается за 1 вечер?

P.S. кружок авиамоделизма в виде перепевания Иерусалимски и QLua.chm не одобряю, смысла в этой деятельности не просматривается. Если бы в статье с этим заголовком, например, обсуждалось что делать с коллбэками DataSource после реконнекта — пользы в ней было бы больше, для начинающих.
Кирилл Гудков, до коллбэков начинающим еще нужно дойти. А на начальных этапах ни хелп, ни мануал разработчиков не сильно помогают (т.к. написаны программистами для программистов, элементарно примеров даже не хватает), а по книгам можно изучить lua, но на этом особо ничего не напишешь, т.к. нужно погружаться именно в специфику реализации его в терминале. Даже форум арки по qlua давно могли бы просто систематизировать, чтобы в нем примеры легче можно было искать. Но разработчикам нет до этого дела, поэтому и получается, что желающие его освоить именно "кружок" энтузиастов, которые должны с разных источников собирать какие-то полезные крупицы, сравнивая постоянно работает это в последней версии терминала или уже нет.
avatar
Synthetic, я считаю, что знать qlua не будет лишним (как знание vba в excel хорошо дополняет работу на более мощных платформах) т.к. иногда можно быстро написать какой-то компактный скрипт (простой индикатор, советник, выгрузка данных, выставление заявок, трейлинг стоп и пр.) без привлечения сторонних библиотек и языков. Простых алгоритмов или несколько роботов запустить, это всё будет вполне устойчиво работать. Что-то более сложное (сотни стратегий торговых, постоянная оптимизация под рынок, с оперативным управлением и ротацией роботов) действительно пишутся уже в связке или полностью на других языках.
avatar
alfacentavra, 

В том-то и дело, что я начал писать на Луа, когда он только появился в Квике, и сегодня я не уверен, что знание Луа — это не лишний, небесполезный груз.
Компактный скрипт можно быстро написать и на Питоне. Если Вы его знаете. Чуть посложнее, типа такого:
from datetime import datetime, date, timedelta, time
from backtrader import Cerebro, TimeFrame
from BackTraderQuik.QKStore import QKStore  # Хранилище QUIK
import Strategy as ts  # Торговые системы
if __name__ == '__main__':  # Точка входа при запуске этого скрипта
    symbol = 'TQBR.SBER'  # Тикер
    store = QKStore()  # Хранилище QUIK (QUIK на локальном компьютере)
    cerebro = Cerebro(stdstats=False)  # Инициируем «движок» BackTrader.
    today = date.today()  # Сегодняшняя дата без времени
    week_ago = today — timedelta(days=7)  # Дата неделю назад без времени  
    data = store.getdata(dataname=symbol, timeframe=TimeFrame.Days, LiveBars=False)
    cerebro.adddata(data)  # Добавляем данные
    cerebro.addstrategy(ts.PrintStatusAndBars)  # Добавляем торговую систему
    cerebro.run()  # Запуск торговой системы
github.com/cia76/BackTraderQuik
 на чистом Луа скорее всего Вы не напишите никогда. Без «сторонних» библиотек вообще нельзя написать ничего серьезного. А задача-то стоит не столько написать, сколько заработать денег на алготорговле. Простые алгоритмы, несколько роботов… это поиграть и забыть.
Что-то более сложное (сотни алгоритмов, постоянная оптимизация под рынок, с оперативным управлением и ротацией стратегий) действительно пишутся уже в связке или полностью на других языках.
 Вот с этим согласен. Но в таких случаях обычно и совсем без Квика как-то обходятся.

PS. За Excel и VBA стоит одна из крупнейших корпораций, десятки лет и сотни миллионов пользователей. Но я про это вообще не в курсе. Как-то R обхожусь.
PPS. Луа — простой язык. Но эта простота из серии «хуже воровства». Наделать невидимых простым глазом ошибок проще простого.

avatar
Synthetic, спасибо за подробности, ссылку и код! Полезно будет и тем, кто сейчас читает статьи, и тем, кто пойдет по этой тропинке позже. Я использую python, но пока очень дозировано, позже коснемся некоторых примеров. Для кого-то возможность автоматизировать один алгоритм уже вполне неплохой вариант. Другой вопрос, что это либо на большом количестве инструментов нужно запускать, либо с постоянной перенастройкой параметров под текущий рынок. Кому-то будет полезно использовать простой трейлинг и он уже качественно улучшит свои результаты. При этом и робота как такого не будет, и погружаться в другие языки не понадобится. А в целом согласен, потому и без всяких оговорок называю это всё "кружок авиамоделизма", профессионалы сразу начинают на C# или C++ писать. Но это больше про команды и большие проекты, с крупными суммами. Частники довольствуются чем-то более простым.
avatar
Константин Чащегоров, :)
Да, выбор большой)
avatar
в индикаторе можно подгружать скрипт из файла?
avatar
autotrade, если я правильно понял вопрос: индикаторы пишутся также на qlua, но по другой структуре и со своими особенностями, у разработчиков на сайте был небольшой мануал с примером, можете загуглить.
avatar
Здравствуйте getScriptPath() работает только с lua или с txt файлами тоже? 
avatar

теги блога alfacentavra

....все тэги



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