Евгений Шибаев
Евгений Шибаев личный блог
17 июля 2020, 19:32

QLua скринер, или то, о чём мечтал Weddy!

Всем, привет!
Мечты сбываются (и не только у Газпрома)! Weddy, надеюсь этим постом я полностью закрываю ваше ТЗ.
QLua скринер, или то, о чём мечтал Weddy!
Суть скринера в вышеизложенном пожелании. Т.к. первый и второй скринеры не удовлетворяли требованиям ТЗ Weddy.
Надеюсь, теперь все ок. Выглядит так:
QLua скринер, или то, о чём мечтал Weddy!
Яндекс — красавчик, однако.
Пользоваться так:
В переменную tickers записываете пары код инструмента = код класса, для тикеров за которыми ведете наблюдение.
tickers = {SiU0 = «SPBFUT», RIU0 = «SPBFUT», BRQ0 = «SPBFUT», GZU0 = «SPBFUT», SRU0 = «SPBFUT», GAZP = «TQBR», SBER = «TQBR», YNDX = «TQBR»,GMKN = «TQBR», MGNT = «TQBR», SU26207RMFS9 = «TQOB»}

В список dates в формате «ДД/ММ/ГГГГ» даты, где от значений хая и лоу тикера в эту конкретную дату и будет рассчитываться текущее процентное изменение.
dates = {«10/07/2020», «02/07/2020», «19/03/2020», «20/01/2020»}
Третий список events, должен соответствовать описанию дат, чтобы понятнее было, что это за дата такая. Например, «01/07/2020» — «Zeroing». Но торги не проводились. В нашем случае мы взяли начало недели, начало месяца и даты, когда индекс ММВБ показал минимум года, и максимум года. Главное, чтобы количество строк описания совпадало с количеством дат. Эти описания и появятся в заголовке столбцов.
events = {«Week », «Month », «MICEX YrL », «MICEX YrH „}
После этого в Квике можно запускать луа-скрипт.
Из приятного: ускорилась загрузка источников данных. Т.к. перенес определение коллбэка из OnInitв Main, давая загрузиться свечам с сервера. Иначе, если сразу определить коллбэк после CreateDataSource, то по мере загрузки свечей, он будет вызываться для каждой загружаемой свечи, т.е. примерно 3000*кол-во_тикеров*2_таблицы раз. Сейчас “работает как часики». Кода, как обычно не много:

-- ©2020 by Evgeny Shibaev for Weddy
-- Таблица, отображающая в процентах рост(падение) инструмента финансового рынка от заданных дат
-- Какие инструменты(тикеры) отслеживаем. Таблица пар тикер - площадка
tickers = {SiU0 = "SPBFUT", RIU0 = "SPBFUT", BRQ0 = "SPBFUT", GZU0 = "SPBFUT", SRU0 = "SPBFUT", GAZP = "TQBR", SBER = "TQBR", YNDX = "TQBR",
           GMKN = "TQBR", MGNT = "TQBR", SU26207RMFS9 = "TQOB"} --IMOEX = "SNDX"
-- От конкретных дат   start month     year low      year high   MICEX INDEX
dates = {"10/07/2020", "02/07/2020", "19/03/2020", "20/01/2020"}
events = {"Week          ", "Month        ", "MICEX YrL ", "MICEX YrH "} -- Список событий, соответствующих заданной выше дате
sources = {} -- Список источников данных по количеству тикеров
rows = {} -- Список строк в таблице LOW по количеству тикеров
screener = AllocTable() -- Указатель на таблицу FromLow
rowsH = {} -- Список строк в таблице HIGH по количеству тикеров
screenerH = AllocTable() -- Указатель на таблицу FromHigh
stopped = false -- Остановка скрипта
col_shift = 1 -- смещение основных столбцов в таблице
local max = math.max  -- локальная ссылка на math.max
local min = math.min  -- локальная ссылка на math.min
     
-- Функция вызывается перед вызовом main
function OnInit(path)
   -- "Ticker"- название первого столбца в таблице
   AddColumn(screener, 0, "Ticker", true, QTABLE_STRING_TYPE, 15)
   AddColumn(screener, 1, "Price", true, QTABLE_DOUBLE_TYPE, 12) 
   -- Названия остальных столбцов в таблице по количеству dates
   for column, date in ipairs(dates) do
       AddColumn(screener, column + col_shift, events[column].." "..date, true, QTABLE_DOUBLE_TYPE, 10)
   end
   CreateWindow(screener)
  -- Даем название  таблице
   SetWindowCaption(screener, "Percent change from LOW from date")
  -- Вторая таблица - от максимумов за период.
   AddColumn(screenerH, 0, "Ticker", true, QTABLE_STRING_TYPE, 15)
   AddColumn(screenerH, 1, "Price", true, QTABLE_DOUBLE_TYPE, 12)   
   -- Названия остальных столбцов в таблице по количеству dates
   for column, date in ipairs(dates) do
       AddColumn(screenerH, column + col_shift, events[column].." "..date, true, QTABLE_DOUBLE_TYPE, 10)
   end
   CreateWindow(screenerH)
   SetWindowCaption(screenerH, "Percent change from HIGH from date")
   for ticker, board in pairs(tickers) do
       --Для каждого тикера создаем источник данных - "дневки"
       sources[ticker] = CreateDataSource(board, ticker, INTERVAL_D1)
       --ИСПРАВЛЕНО! Когда я в OnInit устанавливал коллбэк, то загрузка была долгой, из за того что при приходе очередной свечи 
       -- каждый раз вызывался этот коллбэк. Я перенес определение коллбэка в main с небольшой задержкой sleep(100).
       -- После этого тормоза исчезли. Строка ниже в этом месте тормозила:
       -- sources[ticker]:SetUpdateCallback(function(index) InvalidateCallback(index, ticker) end)
       --Для каждого тикера определяем строку в таблице и запоминаем ее в rows
       rows[ticker] = InsertRow(screener, -1)
       rowsH[ticker] = InsertRow(screenerH, -1)
       --В первом столбце каждой строки устанавливаем имя тикера
       SetCell(screener, rows[ticker], 0, ticker)
       SetCell(screenerH, rowsH[ticker], 0, ticker)
   end
end

-- Коллбэк функция вызывается при изменении значения текущей цены тикера. Обновляет строку тикера в таблице сразу, как происходит изменение.
function InvalidateCallback(index, ticker)
   price = sources[ticker]:C(sources[ticker]:Size())
   ap = getSecurityInfo(tickers[ticker], ticker).scale
   if ap == 0 then price = math.floor(price) end 
   SetCell(screener, rows[ticker], 1, tostring(price))
   SetCell(screenerH, rows[ticker], 1, tostring(price))
   for column, date in ipairs(dates) do
      -- Определяем процентр изменения цены тикера от минимальных значений заданной даты
      percent = DateLow(ticker, date)
      -- Определяем процентр изменения цены тикера за days-дней от максимальных значений  заданной даты
      percentH = DateHigh(ticker, date)
      SetCell(screener, rows[ticker], column + col_shift, string.format("%.2f", percent))
      SetCell(screenerH, rowsH[ticker], column + col_shift, string.format("%.2f", percentH))
      -- Подкрашиваем ячейку соответственно росту(падению) и величины роста(падения)
      SetColor(screener, rows[ticker], column + col_shift, BCellColor(percent), FCellColor(percent), BCellColor(percent), FCellColor(percent))
      SetColor(screenerH, rowsH[ticker], column + col_shift, BCellColor(percentH), FCellColor(percentH), BCellColor(percentH), FCellColor(percentH))
   end
end

-- Определяет процент изменения текущей цены от минимума дня указанной даты
function DateLow(ticker, date)
  local len = sources[ticker]:Size() --Сколько всего "дневных" свечей в источнике данных конкретного тикера
  local dt = StrToDate(date)
  for i = len, 1, -1 do
    local candle_time = sources[ticker]:T(i)
    if dt.day == candle_time.day and dt.month == candle_time.month and dt.year == candle_time.year then
       return (sources[ticker]:C(len) - sources[ticker]:L(i)) / sources[ticker]:L(i) * 100 end
  end
  return 0
end

-- Определяет процент изменения текущей цены от максимума дня указанной даты
function DateHigh(ticker, date)
  local len = sources[ticker]:Size() --Сколько всего "дневных" свечей в источнике данных конкретного тикера
  local dt = StrToDate(date)
  for i = len, 1, -1 do
    local candle_time = sources[ticker]:T(i)
    if dt.day == candle_time.day and dt.month == candle_time.month and dt.year == candle_time.year then
       return (sources[ticker]:C(len) - sources[ticker]:H(i)) / sources[ticker]:H(i) * 100 end
  end
  return 0
end

-- Цвет текста в ячейке. Если рост - то цвет "зеленый", падение - "красный"
--function FCellColor(change) if change > 0 then return RGB(0,128,0) else return RGB(158,0,0) end end
function FCellColor(change) if change > 0 then return RGB(0,0,0) else return RGB(0,0,0) end end

-- Маленькая "тепловая карта". Делает фон ячейки более интенсивным, взависимости от величины роста(падения)
function BCellColor(change)
  bright = math.floor(255 - min(math.abs(change*5), 235),1)  --10 110
  if change > 0 then return RGB(bright,255,bright) else return RGB(255,bright,bright) end
end

-- Преобразует строку вида "05/07/2020" в таблицу {year = 2020, month = 7, day = 5}
function StrToDate(str)
   dt = {}
   dt.day,dt.month,dt.year = string.match(str, "(%d*)/(%d*)/(%d*)")
   for key,val in pairs(dt) do dt[key] = tonumber(val) end
   return dt
end

-- Функция вызывается перед остановкой скрипта
function OnStop(signal) stopped = true end

-- Функция вызывается перед закрытием квика
function OnClose() stopped = true end;

-- Основная функция выполнения скрипта
function main()
   for ticker, board in pairs(tickers) do
       sleep(300)
       --Устанавливаем коллбэк функцию для каждого тикера. Она вызывается при изменении цены тикера. "А что, так можно было?"
       sources[ticker]:SetUpdateCallback(function(index) InvalidateCallback(index, ticker) end)
       sleep(50)
       -- Вызываем первый раз явно для полной отрисовки таблицы, следующая отрисовка строк только при изменении цены
       InvalidateCallback(sources[ticker]:Size(), ticker)
   end
  while not stopped do sleep(1) end
end  	  

Всем хороших выходных, и пусть мечты сбываются!
Код вы можете также найти в конце страницы https://www.jatotrade.com/download
Или по прямой ссылке. Подписаться на мой канал в ютьюбе можно здесь. Там много чего интересного.





25 Комментариев
  • 3Qu
    17 июля 2020, 21:59
    Из замечаний.

    function OnStop(signal)
    stopped =true
    sleep(1000) — неплохо бы поставить...
    end

    sleep(1000) — неплохо бы поставить, чтобы main() могла корректно завершиться.
    и в OnClose() тоже хорошо бы
  • Vadim Ch
    17 июля 2020, 22:18
    Огонь!
  • 3Qu
    17 июля 2020, 23:09
    Еще один момент обнаружил.
    Вы делаете подписки в цикле
    sources[ticker] = CreateDataSource(...)
    В OnStop() и в OnClose() их, по хорошему надо бы уничтожить.
    примерно так:
    sources[ticker]:Close()
    Наверно опять же в цикле.
    Можно это дело перенести в main() после завершения его основного цикла.
      • 3Qu
        17 июля 2020, 23:28
        Евгений Шибаев, я так понимаю, что подписки идут на уровне терминала, примерно на том же уровне, что и подписки на данные в меню настроек Квик, и наверно остаются до перезагрузки терминала. Не знаю насколько они чему могут помешать, но постель лучше за собой убирать.) На всяк случай.
        Это мое понимание, утверждать ничего не берусь.
  • Vladimir N.
    17 июля 2020, 23:21
    Как язык на Си похож. Наверное, Lua потом зайдет.
    • 3Qu
      18 июля 2020, 01:05
      Vladimir N., пожалуй, по синтаксису он ближе к VB, только без V.)
  • Евгений Гуревич
    18 июля 2020, 07:01
    А мне lua паскаль напоминает, не один а один, но что-то есть ))
  • Chelovekspasibo
    23 июля 2020, 14:59
    Жень, здравствуй, в чём может быть печалька:


    Сам скрипт при этом не стартует. Спасибо.
  • Емельянов Иван
    15 августа 2020, 09:58
    Здравствуйте, спасибо за ваш труд, но что-то давно у вас не было топиков:) Могли бы вы запилиль qlua скрипт для выгрузки тиковых данных по акциям в sqlite? Был бы крайне признателен!
      • GoodBargains
        02 июня 2022, 15:38
        Евгений Шибаев, прощу прощения за беспокойство! Но ошибкка выскакивает почему то при запуске данного скрипта вот такая:

        Может Вам не сложно сказать в чем дело?)

          • GoodBargains
            03 июня 2022, 10:43
            Евгений Шибаев, странно, но уже нет ошибки. Вчера определенно была. 
          • GoodBargains
            03 июня 2022, 18:05
            Евгений Шибаев, а сейчас опять та же ошибка появилась(. Вот отсюда скрипт smart-lab.ru/blog/630269.php. А еще не подскажите, как доделать, чтобы память не забивалась? Все время растет память, тогда как строк всегда одинаковое количество. вот тут память просто растет прямо пропорционально времени:


              • GoodBargains
                05 июня 2022, 11:36
                Евгений Шибаев, а как сделать, чтобы сохранялись только N последних? Или это и есть скользящая рамка?
                • GoodBargains
                  07 июня 2022, 21:00
                  Евгений Шибаев, а так можно ограничить в LUA в квике? Может пару строк кода всего  не подскажете?
                • GoodBargains
                  13 июня 2022, 14:05
                  Евгений Шибаев, хотел ещё у вас спросить, почему на Лисп быстрее, чем на Питоне было бы? За счет чего быстрее то?
  • Кашников Александр
    14 января 2022, 15:11
    код так себе, даты нужно переводить в секунды и потом 1 раз сравнивать.

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

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