--- Использование графического пакета IUP (Lua 5.4...) в QUIKе (! ограничение IUP:
-- только в одном из запущенном скрипте экземпляра QUIK).
-- Доступны все возможности пакета IUP (подключаемой версии).
-- Вариант 1: запуск IUP в потоке main (надо подключать только IUP, но цикл обработки скрипта программировать
-- в таймере IUP: помечено #### ).
-- Вариант 2: запуск IUP в отдельном потоке отличном от main(кроме IUP, обязательно подключать пакет QluaUser).
-- Вариант 1 более безопасный, чем 2. Так как однопоточный, и не требующий синхронизации диалога с
-- основной работой скрипта.
-- Ссылка для скачивания пакетов iup.dll, iuplua54.dll, QluaUser.dll (мой):
-- https://cloud.mail.ru/public/7xAm/jaCgULqGo Перед использованием пакетов, надо их разблокировать.
-- Пакеты переслать в папку экземпляра QUIK, хранящую файл info.exe.
_RUN_ = true
function main()
-- Пример формы IUP со двумя вкладками ---
local Pause = 1000
local thread = false -- false - выполнение диалога в потоке main; true - запуск IUP в отдельном потоке --
local cpath = (getWorkingFolder() .. '\\?54.dll;' .. getWorkingFolder() .. '\\?_5_4.dll;'.. getWorkingFolder() .. '\\?.dll;' .. package.cpath)
---
package.cpath = cpath
require("iuplua")
local QluaUser = require('QluaUser') -- Это для запуска диалога IUP в отдельном потоке ---
if thread then
QluaUser = require('QluaUser')
end
local function iup_form()
local timer = iup.timer { time = Pause } ---- Вместо sleep таймер формы ( млсек.) ---
timer.run = "NO"
function timer:action_cb()
if not _RUN_ then
timer.run = "NO"
iup:ExitLoop() -- Завершает текущий диалог
end
if not thread then -- Основной цикл обработки скрипта в таймере IUP ---
-- Тело основного цикла скрипта -- ####
message('iup выполняется в потоке main. Тело основного цикла скрипта должно выполняеться здесь (в таймере iup)')
-------------------
end
end
-- Creates boxes
local vboxA = iup.vbox{iup.fill{}, iup.label{title="TABS AAA", expand="HORIZONTAL"}, iup.button{title="AAA"}}
local vboxB = iup.vbox{iup.label{title="TABS BBB"}, iup.button{title="BBB"}}
-- Sets titles of the vboxes
vboxA.tabtitle = "AAAAAA"
vboxB.tabtitle = "BBBBBB"
-- Creates tabs
local tabs = iup.tabs{vboxA, vboxB}
-- Creates dialog
local dlg = iup.dialog{iup.vbox{tabs; margin="10x10"}; title="Test IupTabs", size="150x80"}
-- Shows dialog in the center of the screen
dlg:showxy(iup.CENTER, iup.CENTER)
timer.run = "YES"
if (iup.MainLoopLevel()==0) then
iup.MainLoop()
end
timer.run = "NO"
end
if thread then
QluaUser.ThreadNew(iup_form)
else
iup_form()
iup.Close()
iup = nil
end
while _RUN_ do -- Основной цикл обработки скрипта в main ---
-----------------------
if thread then
message('iup был запущен в отдельном потоке. Тело основного цикла скрипта выполняется в потоке main.')
else
message('После завершения потока iup, продолжается выполняется поток main.')
end
sleep(Pause)
-----------------------
end
end
---
function OnStop(flag)
_RUN_ = false
sleep(2000)
if iup then iup.Close() end
return 3000
end
Просто к слову, т.к. используется неизвестная библиотека QluaUser.dll то запускать это не видя исходники будут сложно. По крайней мере мне, не знаю как другим.
Nikolay написал: Просто к слову, т.к. используется неизвестная библиотека QluaUser.dll то запускать это не видя исходники будут сложно. По крайней мере мне, не знаю как другим.
Цитата
TGB написал: -- Вариант 1: запуск IUP в потоке main (надо подключать только IUP, но цикл обработки скрипта программировать -- в таймере IUP: помечено #### ). -- Вариант 2: запуск IUP в отдельном потоке отличном от main(кроме IUP, обязательно подключать пакет QluaUser). -- Вариант 1 более безопасный, чем 2. Так как однопоточный, и не требующий синхронизации диалога с -- основной работой скрипта.
Nikolay написал: Просто к слову, т.к. используется неизвестная библиотека QluaUser.dll то запускать это не видя исходники будут сложно. По крайней мере мне, не знаю как другим.
Цитата
TGB написал: -- Вариант 1: запуск IUP в потоке main (надо подключать только IUP, но цикл обработки скрипта программировать -- в таймере IUP: помечено #### ). -- Вариант 2: запуск IUP в отдельном потоке отличном от main(кроме IUP, обязательно подключать пакет QluaUser). -- Вариант 1 более безопасный, чем 2. Так как однопоточный, и не требующий синхронизации диалога с -- основной работой скрипта.
Я понял про что это библиотека. Я говорил, что нет исходников.
Согласен, в работающем роботе достаточно возможностей пользовательских таблиц QUIK. Идеальный, зарабатывающий робот должен это делать без вводных и без лишнего вывода, только отчеты о прибыли в журнале с аналитикой, полезной пользователю. Реально, желателен вывод роботом сообщений о наступлении событий, требующих вмешательство пользователя. Все это без проблем реализуемо на таблицах QUIK. Графический пакет я использовал для реализации средств создания скриптов, что сделать на таблицах QUIK сложновато.
Вообще-то есть и третий способ использования IUP в Quik'е ttps://forum.quik.ru/forum10/topic9267/Со вполне чётко разъяснённой мотивацией и механикой - параллельное исполнение скрипта main() и ручных манипуляций в IUP с непрерывной связью между этими нитями (threads) в обоих направлениях. Переключение сопрограмм-корутин (coroutines) по таймеру между потоками управления в main() и IUP должно быть эффективнее любого другого способа.
А вот ещё четвёртый способ - запустить IUP из отдельного скрипта со своим main(), а ещё лучше - отдельным от Quik'а приложением; тогда не будет лишней нагрузки на Quik. И держать двустороннюю связь между торговым скриптом и IUP через DDE. Это создаст ещё меньшие накладные расходы (в основе DDE - оконные процедуры Windows), и отпадает необходимость переключения по таймеру - связь только когда возникает реальная потребность обмена.
Ростислав Дм. Кудряшов написал: параллельное исполнение скрипта main() и ручных манипуляций в IUP с непрерывной связью между этими нитями (threads) в обоих направлениях.
Где вы прочитали, что корутины в Lua выполняются параллельно? Интересно, как в одном потоке можно с помощью корутин реализовать параллелизм (одновременность) их выполнения? Но на всякий случай цитата Р. Иерузалимски: "в любой момент времени программа с сопрограммами выполняет только одну из своих сопрограмм".
Ростислав Дм. Кудряшов написал: параллельное исполнение скрипта main() и ручных манипуляций в IUP с непрерывной связью между этими нитями (threads) в обоих направлениях.
Где вы прочитали, что корутины в Lua выполняются параллельно? Интересно, как в одном потоке можно с помощью корутин реализовать параллелизм (одновременность) их выполнения? Но на всякий случай цитата Р. Иерузалимски: "в любой момент времени программа с сопрограммами выполняет только одну из своих сопрограмм".
Ростислав Дм. Кудряшов написал: параллельное исполнение скрипта main() и ручных манипуляций в IUP с непрерывной связью между этими нитями (threads) в обоих направлениях.
Где вы прочитали, что корутины в Lua выполняются параллельно? Интересно, как в одном потоке можно с помощью корутин реализовать параллелизм (одновременность) их выполнения? Но на всякий случай цитата Р. Иерузалимски: "в любой момент времени программа с сопрограммами выполняет только одну из своих сопрограмм".
Не поленись, а запусти на своём ПК пример переключения спорограмм-корутин между IUP и main(). И убедишься, что невытесняющая (кооперативная) многозадачность порой лучше вытесняющей.
Ростислав Дм. Кудряшов написал: Не поленись, а запусти на своём ПК пример переключения спорограмм-корутин между IUP и main(). И убедишься, что невытесняющая (кооперативная) многозадачность порой лучше вытесняющей.
С этого места, пожалуйста, поподробнее. Как вам удалось сумев запустить единственный готовый пример, определить что он порой лучше вытесняющей многозадачности? С чем вы его сравнивали? Или вы можете определять лучшее без сравнения? Откуда к вам приходят такие озарения ? Поделитесь.
Опять эти разговоры про параллельное исполнение. Lua - синхронное, однопоточное изделие. Корутины - это просто созданный стек и выполнение через переключение. Работать может либо тело скрипта, либо корутина, но не параллельное исполнение. Как только вы начинаете с помощью внешних средств пробовать сделать параллельное исполнение, то сразу же возникает вопрос о защите стека, т.к. всегда возникнет ситуация когда кто-то пишет в стек, а другой, параллельный, читает и очищает стек. Квик в этом плане падает сразу, если небрежно уронить стек скрипта. Что отдельная тема, прочему скрипт убивает весь терминал.
Так что реализация работы с одним стеком через потоки - это не такая простая задача, и точно не про корутины, которые не стоит путать с таковыми в других языках, приписывая им те же свойства. Lua - это продукт из 90-х.
От теории к смыслам! Вся прелесть и заключается использования сопрограмм в превращении выполнения одной последовательной задачи луа в ряд независимых задач! Как добиваемся независимости, опросом сопрограмм, там где нужно и тогда когда нужно!
Вот мой рабочий пример (как есть прямо в таком виде сейчас крутится, это не образец для подражания, но), что я только с ним не делал? И сейчас на вечерней падает сопрограмма - обработка таблицы всех сделок, ну и ладно скрипт работает, а с вечеркой когда руки дойдут разберусь.
Код
function OnMain()
Log:trace("OnMain started")
-- Создаем корутины Прверка пароля
local coPassword = coroutine.create(checkPassword)
-- Прверка пароля при первом входе!(корутина с безопасным вызовом)
safe_resume(coPassword, "coPassword")
--[[GUI.initialize()
-- Создание таблицы ордеров
GUI.createTable({
name = "orders",
title = "Active Orders",
width = 1000,
height = 400,
x = 10,
y = 50,
columns = {
{
iCode = 1,
title = "Time",
par_type = QTABLE_TIME_TYPE,
width = 120
},
{
iCode = 2,
title = "Price",
par_type = QTABLE_DOUBLE_TYPE,
width = 100
},
{
iCode = 3,
title = "Volume",
par_type = QTABLE_INT64_TYPE,
width = 100
}
}
})
-- Создание информационной метки
GUI.createLabel({
id = "statusLabel",
text = "System Status: OK",
x = 10,
y = 10,
width = 300,
color = "#00FF00",
bgColor = "#333333"
})
--]]
GUI.initialize()
-- Создание таблицы ордеров
GUI.createTable({
name = "orders",
title = "Активные ордера",
columns = {
{
iCode = 1,
title = "Время",
type = QTABLE_TIME_TYPE,
width = 120
},
{
iCode = 2,
title = "Операция",
type = QTABLE_STRING_TYPE,
width = 100
},
{
iCode = 3,
title = "Объем",
type = QTABLE_INT_TYPE, -- Теперь правильно QTABLE_INT_TYPE = 1
width = 80
}
}
})
-- Пример использования в главном цикле
local hook = PerformanceHook()
--local performanceHook = PerformanceHook()
local fatal = nil
AutoUpdateVersion()
CreateWindowRobot()
if Start ~= nil then
Start()
end
AtExit(function()
for _, so in pairs(SmartOrder.pool) do
so:enough()
so:process()
end
end)
if Robot ~= nil then -- Провека на наличие основтой функции
---- функция для получения информации о классе
local ClassInfo = getClassInfo(class_names[1]);
firmid = ClassInfo and ClassInfo.firmid or ''; Log:trace('firmid = ' .. tostring(firmid ) );
-- Создаем корутину для функции Robot
local routine = coroutine.create(Robot)
Log:trace("Robot started")
-- Создаем корутины
--local coPassword = coroutine.create(checkPassword)
local coConnection = coroutine.create(checkConnection)
local coTradeDate = coroutine.create(checkTradeDate)
local coServerTime = coroutine.create(checkServerTime)
local coWorkTime = coroutine.create(checkWorkTime) -- Сопрограмма для отслеживания рабочего времени
--local coStopTimeTrading = coroutine.create(checkStopTimeTrading)
-- Cоздание корутин для стратегий реального времени
--if not rejime.test and event_co.connected == 1 and event_co.servertime[2] and flagTimeTrading then
-- Cоздание корутины function
local coCapitalManager = coroutine.create(CapitalManagerCo--function() CapitalManagerCo(CONFIG) end
)
local coMoneyManagement = coroutine.create(function()
--for _, class in ipairs(class_names) do
--for n, symbol in ipairs(symbol_names) do
--if mm[class][symbol] ~= nil then
--MoneyManagementCo( mm[class][symbol] )
MoneyManagementCo( firmid, account, class_names, symbol_names )
--else
--Log:error("MoneyManagement instance 'mm[n]' is nil. Cannot start MoneyManagementCoroutine.")
--end
--end
--end
end)
--if not rejime.test and event_co.connected == 1 and event_co.servertime[2] and flagTimeTrading then
local co_alltrade = coroutine.create(alltrade_processor)
local co_order_book = coroutine.create(order_book_processor)
--local co_tt_parametrs = coroutine.create(trade_data_processor)
local futuresCoroutine = coroutine.create(futures_position_processor)
local futuresLimitCoroutine = coroutine.create(futures_limit_processor)
--end
while WORKING_FLAG do
--------------------------
local sek = os.clock();
--------------------------
--[[-- Обновление данных
--GUI.update_connection_status(isConnected())
--GUI.update_last_activity(os.time())
--GUI.update_uptime(getUptime())
-- Добавление новых ордеров
--while hasNewOrders() do
-- GUI.add_order(getNextOrder())
--end
-- Обновление торговой панели
GUI.updateComponent("trading_panel", {
{"BUY", 100.50, 10},
{"SELL", 101.00, 5}
})
-- Добавление метки на график
GUI.Chart.addLabel("MAIN_CHART", "Support Level", {
YVALUE = 98.50,
COLOR = RGB(46, 204, 113)
})--]]
-- Обновление данных
GUI.updateTable("orders", {
{os.date("%H:%M:%S"), "BUY", 10},
{os.date("%H:%M:%S"), "SELL", 5}
})
-- Запуск корутин с безопасным вызовом
if not rejime.test then
safe_resume(coConnection, "coConnection")
SetCell(table_id, 1, 1, tostring(event_co.connected == 1 and 'Connect' or 'NoConnect'))
if event_co.connected == 1 then
safe_resume(coTradeDate, "coTradeDate")
safe_resume(coServerTime, "coServerTime")
safe_resume(coWorkTime, "coWorkTime")
--safe_resume(coStopTimeTrading, "coStopTimeTrading")
--if event_co.connected == 1 then
-- Запуск корутины MoneyManagement
--safe_resume( MoneyManagementRoutine, "MoneyManagementRoutine")
safe_resume( coCapitalManager, "coCapitalManager")
--end
-- Запускаем корутину (История сделок и стакан)
--Log:info('tradedate[1] =' ..tostring(event_co.tradedate[1]) ..'; worktime =' ..tostring(event_co.worktime[2]) ..'; servertime[2] =' ..tostring(event_co.servertime[2]) --..'; flagTimeTrading =' ..tostring(flagTimeTrading) )
if event_co.tradedate[1]
and event_co.worktime[2]
and event_co.servertime[2]
then
safe_resume(co_alltrade, 'co_alltrade')
safe_resume(co_order_book, 'co_order_book')
end
--coroutine.resume(co_tt_parametrs)
-- Функция для запуска корутины
--start_futures_position_processing(futuresCoroutine)
--start_futures_limit_processing(futuresLimitCoroutine)
safe_resume(futuresCoroutine, 'futuresCoroutine')
--safe_resume(futuresLimitCoroutine, 'futuresLimitCoroutine')
end
-- Формирем модуль СМ
--safe_resume( coCapitalManager, "coCapitalManager")
--safe_resume( coMoneyManagement, "coMoneyManagement")
end
-- co Robot Затем используйте его методы
local res, errmsg = coroutine.resume(routine)
if not res then
fatal = "Broken coroutine (Robot): " .. errmsg
-- Вариант 1: Использование глобального обработчика (рекомендуется)
_G.global_error_handler:handle_error(fatal, "Robot")
-- Вариант 2: Статический вызов
-- ErrorHandler.handle_error_static(fatal, "Robot")
break
end
if coroutine.status(routine) == "dead" then
Log:trace("Robot routine finished")
break
end
ProcessRegistered()
--local elapsed_ms = performanceHook(sek) -- sleep(1000)
hook(sek) -- Обновляем метрики
sleep(spinner.config.interval * 1000) -- Контроль частоты
--if rejime.test then
-- sleep(100)
--else
-- Log:trace(math.floor(1000 - tonumber(elapsed_ms)))
--sleep( )
-- sleep(1000)
--end
end
end
if Stop ~= nil then Stop() end
ProcessAtExit() -- Вызов ProcessAtExit
ErrorCollector.log_all_errors() -- Логирование всех ошибок при завершении
Log:trace("Robot stopped")
if fatal ~= nil then
error(fatal)
end
end
Конкретные смыслы и практические преимущества, или как сопрограммы меняют парадигму!
Смысл 1: Простота - читаемость и ясность кода. --------------------------------------------------------
* Теория: Мы пишем последовательный код. * Смысл: Мы можем описать сложную асинхронную логику так, как мы о ней думаем — шаг за шагом, без нагромождения хаоса колбэков. "Ад колбэков" — код растет вглубь, асинхронная логика размазана по функциям. После (красота сопрограмм)!
Код
function сложнаяЗадача()
local data = загрузитьДанные("url") -- Здесь может быть yield
local result = обработатьДанные(data) -- И здесь
local success = сохранитьВБД(result) -- И здесь
если success then
обновитьИнтерфейс()
end
end
-- Создаем сопрограмму и "опросом" управляем ее выполнением
local task = coroutine.create(сложная Задача)
Смысл: Код выглядит как обычный, линейный и "блокирующий", но на деле он асинхронный. Мозг читает его без напряжения.
Смысл 2: Архитектор - "Кооперативная многозадачность" и контроль. ------------------------------------------------------------ * Теория: Мы сами решаем, когда отдать управление. * Смысл: Мы становимся архитекторами своего планировщика. Мы не зависим от прерываний по таймеру, а сами говорим: "Стоп, я сейчас подожду, а ты пока выполни что-то еще".
Код
-- Пул из нескольких "независимых задач", как вы и сказали
local tasks = {
coroutine.create(function() ... end), -- Задача 1: загрузка контента
coroutine.create(function() ... end), -- Задача 2: анимация UI
coroutine.create(function() ... end), -- Задача 3: обработка ввода
}
-- Главный цикл "опросом" управляет всеми задачами
function главныйЦикл()
while true do
local всеЗавершены = true
for i, co in ipairs(tasks) do
if coroutine.status(co) ~= "dead" then
всеЗавершены = false
coroutine.resume(co) -- "Опрос" конкретной сопрограммы!
end
end
если все Завершены then break end
end
end
* Смысл: Мы точно знаем, в какой момент какая задача будет выполняться. Нет гонки данных, нет сложных примитивов синхронизации, как в потоках. Мы создаем детерминированную систему.
Смысл 3: Эффективность - эффективное ожидание и реактивность. ------------------------------------------------------------------------------------------------------ * Теория: Сопрограмма может быть приостановлена в любой точке. * Смысл: Мы можем элегантно реализовать ожидание без блокировки всего приложения.
Код
-- В движке: ждем 2 секунды перед загрузкой данных.
function ждать(секунды)
local времяСтарта = os.clock()
while os.clock() - времяСтарта < секунды do
coroutine.yield() -- "Спим", не блокируя основной поток, отдавая управление другим задачам
end
end
* Смысл:** Мы пишем логику, которая "живет" во времени, но не замораживает весь мир вокруг себя. Это основа для AI, анимаций, диалогов в играх.
Смысл 4: Эффективность - генераторы и бесконечные последовательности. ------------------------------------------------------------------------------------------------------------------ * Теория: Сопрограмма может yield-ить промежуточные результаты. * Смысл: Мы создаем "ленивые" вычисления и потенциально бесконечные потоки данных.
Код
function бесконечнаяПоследовательность(старт, шаг)
local текущее = старт
while true do
coroutine.yield(текущее)
текущее = текущее + шаг
end
end
-- Создаем генератор чисел, начиная с 10, с шагом 5
local генератор = coroutine.wrap(бесконечнаяПоследовательность)
local gen = генератор(10, 5)
print(gen()) --> 10
print(gen()) --> 15
print(gen()) --> 20
-- Можно получать значения по требованию, не вычисляя все сразу.
* Смысл: Экономия памяти и вычислений. Мы рассчитываем следующее значение только тогда, когда оно реально нужно.
Итого. Вся прелесть — в этом "превращении". Сопрограммы в Lua — это мост между двумя мирами: 1. Мир для программиста: Удобный, последовательный, понятный код. 2. Мир для машины: Гибкое, асинхронное, неблокирующее выполнение.
И ключ к управлению этим миром — наш "опрос" с помощью `coroutine.resume`. Это диалог: "Ты готова поработать?", "Сделай шаг", "Отдохни, я спрошу тебя позже". Это и есть тот самый глубокий смысл, который делает сопрограммы в Lua таким элегантным и мощным инструментом.