swerg, давайте по порядку. Чтобы вызвать любую Lua-функцию из Си-кода нужно вызвать lua_call или lua_pcall. Остановлюсь на втором методе, т.к. именно его использует Quik для вызова main, всех callback'ов и загрузки скрипта (через Lua-функцию require или dofile). Вызов lua_pcall выполняет lua_lock. Также напомню, что lua_lock / lua_unlock работают с одной и той же критической секцией, которая обслуживает сразу два потока (main и callback'ов), поэтому, если в другом потоке эта критическая секция будет заблокирована, то поток, вызывающий lua_lock, будет ждать её освобождения. Итак, в lua_pcall мы блокируем нашу критическую секцию lua_lock, затем по цепочке вызовов lua_pcall -> luaD_pcall -> luaD_rawrunprotected -> LUAI_TRY (этот макрос вызывает на самом деле f_call - см. параметр ф-ции luaD_rawrunprotected) -> luaD_call -> мы наконец попадаем в luaV_execute (в случае, если наша функция - это Lua-функция). Замечу, что всё это время мы удерживаем критическую секцию (lua_lock). Освободить критическую секцию, пока выполяется luaV_execute мы можем по трём причинам (см. код luaV_execute): 1. dojump использует luai_threadyield, который позволяет переключиться на выполнение другого потока, 2. luaD_precall в случае вызова Си-функции из Lua-функции (например Sleep), 3. traceexec выполняется на каждой итерации LuaVM и, в случае если установлен debug.sethook, luaD_callhook выполнит lua_unlock / lua_lock.
Также замечу, что LuaVM (luaV_execute) не использует LuaAPI (функции из lapi.c) для доступа к переменным. LuaVM использует внутренние функции (которые как раз и вызывают функции из lapi.c) напрямую - без блокировки критической секции (lua_lock / lua_unlock), потому что, когда выполняется luaV_execute, критическая секция уже заблокирована.
Да, создаются 2 системных потока, да, в каждом из них выполняется lua_pcall, который приводит к вызову luaV_execute, но пока выполняется код самой luaV_execute, код в другом потоке ждёт на lua_lock или выполняет что-нибудь другое.
P.S. Теоритически, чтобы заставить LuaVM долго не отпускать критическую секцию нужно выполнить debug.sethook(nil), а затем выполнять в одном из потоков линейный Lua-код, который не приведёт к вызову dojump.
swerg написал: И да, код в смысле Lua-программы прерывается в реализованном случае вовсе не обязательно "между операторами Lua". Прерваться код может и "посередине выполнения одного из операторов".
Да, выполнение любого потока может прерваться в любом месте. Что я имею ввиду, так это то, что 2 Lua-команды (из потока main и потока callback'а) одновременно выполняться не могут: если выполнение потока main будет приостановлено системным планировщиком на середине выполнения Lua-команды (luaV_execute) и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции (планировщик переключит выполнение на другой поток вместо основного потока Quik), т. к. поток main в данный момент владеет мьютексом, который предотвращает одновременное выполнение двух Lua-комманд из разных потоков.
Yaroslav1357, в lua уже есть подобный функционал (исключения)
Код
error("Сообщение об ошибке");
как раз моментально прекратит выполнение скрипта.
Чтобы перехватывать исключения есть метод pcall
Код
function bad(str)
message(str, 1);
local t
t[1] = nil;
end
function main()
local success, err = pcall(bad, "test string");
if not success then
message("Error: " .. err);
end
end
Сразу замечу, что в качестве аргумента метода error() можно передавать любой Lua тип. Он придёт во втором результате pcall без изменений.
Кроме того, нужно иметь в виду, что QLua вызывает каждый callback, метод main, и инициализацию нашего скрипта через тот же pcall (через Си-функцию lua_pcall, если быть совсем точным), т.е. QLua таким же образом получает сообщение об ошибке в нашем скрипте.
Предполагаю, что QLua использует отладочные функции debug.sethook для перехвата управления и переключения Lua VM c потока main на поток callback'ов. Других варинтов не вижу, как можно (без переписывания самого движка Lua) переключить Lua VM на выполнение другого потока даже в случае бесконечного цикла
Код
while(true) do end
(кроме того, нашёл код в Lua: lvm.c макрос dojump, который как раз и будет выполняться в случае с безусловным переходом, и который выполняет unlock / lock, поэтому бесконечный цикл точно не заблокирует Lua VM навсегда, а с использованием debug.sethook заблокировать Lua VM навсегда в принципе становится невозможно)
В таком случае, лишь одна команда Lua VM выполняется атомарно, а затем может произойти переключение на выполнение другого Lua потока. Отсюда следует, что использование библиотеки, реализующей API доступа к системным мьютексам (или критическим секциям, как будет угодно), будет вполне оправдано для разграничения доступа к глобальным Lua таблицам.
Не ожидаю увидеть заметную потерю производительности в этом случае, т.к. QLua на каждую Lua команду выполняет unlock / lock мьютекса, ограничевающего доступ к Lua VM (через обращение к callback'у, установленному с помощью debug.sethook).
Евгений Петров, если допускается, что сам Quik или .dll плагин в нём могут "упасть", то вариант Валентина на мой взгляд, лучший из возможных. если падения наблюдаются в самом скрипте (ошибки Lua), то можно написать к нему обёртку такого вида, которая будет проксировать все вызовы функций основного скрипта.
Код
local MAIN_SCRIPT = "main_script";
-- в Lua 5.1 не функции table.pack, которая присутствует в Lua 5.2
function table.pack(...)
return { n=select('#', ...); ... }
end
table.unpack = unpack;
package.path = string.format('%s;%s\\?.lua', package.path, getScriptPath());
if not pcall(require, MAIN_SCRIPT) then -- безопасно загружаем main_script.lua (основной скрипт) из папки с этим скриптом
error("Error loading " .. MAIN_SCRIPT .. ".lua");
end
local function create_proxy(func_name)
if (_G[func_name] == nil) then
return;
end
local old = _G[func_name];
_G[func_name] = function(...)
local res = table.pack(pcall(old, ...));
if res[1] then
return table.unpack(res, 2, res.n);
else
-- вызов функции завершился с ошибкой
-- здесь можно выполнить любого рода код, сообщающий об ошибке.
-- Чтобы прикратить выполнение скрипта в этот момент с той же
-- ошибкой, которая произошла в вызываемом коде, достаточно:
error(res[2]);
end
end
end
-- список функций, которые вызывает Quik
local func_list = {
"main",
-- список callback'ов из документации Quik 7.1.1.16
"OnAccountBalance",
"OnAccountPosition",
"OnAllTrade",
"OnCleanUp",
"OnClose",
"OnConnected",
"OnDepoLimit",
"OnDepoLimitDelete",
"OnDisconnected",
"OnFirm",
"OnFuturesClientHolding",
"OnFuturesLimitChange",
"OnFuturesLimitDelete",
"OnInit",
"OnMoneyLimit",
"OnMoneyLimitDelete",
"OnNegDeal",
"OnNegTrade",
"OnOrder",
"OnParam",
"OnQuote",
"OnStop",
"OnStopOrder",
"OnTrade",
"OnTransReply",
};
for _,v in ipairs(func_list) do
create_proxy(v);
end
Ярослав С, по моему опыту, хранить всё равно в каком формате, т.к. загрузка / сохранение выполняться будут редко. Кроме того, привязка к какому-нибудь самописному бинарному формату потребует поддержки в будущем.
Рекомендую вот этот пример http://lua-users.org/wiki/SaveTableToFile Все нужные параметры сохраните в таблицу и затем сохраняйте/загружайте её. Формат сохранения -- lua-файл -- будет очевиден после возвращения к разрабатываемому коду.
Николай Камынин написал: У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Я собственно спросил зачем мьютекс если один процесс, в том плане, что в одном процессе синхронизацию потоков эффективнее делать другими средствами либо в пользовательском режиме -атомарным операциями и критическими секциями либо ядерными - событиями. Это быстрее , чем мьютекс.
Я не понял контекст вашего оригинального сообщения. Есть просто термин мьютекс, есть std::mutex (или std::recursive_mutex - по сути реализованые через аналог критических секций на уровне Concurrency namespace'а в MS CRT), и есть WinAPI mutex, который может использоваться несколькими процессами сразу. В первом сообщении я сразу упомянул, что имею ввиду std::recursive_mutex или критические секции, а дальше уже оба варианта называл просто "мьютексом", чтобы не писать одно и то же по нескольку раз.
Вячеслав написал: Поэтому же Quik можно подвесить простым кодомКодwhile (true) do end Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback. В результате, Quik зависнит
нет не зависнет
Тогда в какой момент произойдёт переключение выполнения потока main на выполнение callback'а? Ведь в main таким кодом мы монопольно занимаем Lua VM.
swerg, Подытожу: в Lua переключение потоков (глобального на main и обратно) происходит только в момент вызова любой Си функции (любой библиотечной функции). В остальное время, если Lua-код выполняется, то он выполняется атомарно. Поэтому же Quik можно подвесить простым кодом
Код
while (true) do end
Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback. В результате, Quik зависнит на lua_lock Си функции, т.к. Lua VM будет в этот момент занята потоком main, выполняющим наш бесконечный цикл.
Николай Камынин написал: У меня вопрос к автору темы. А зачем использовать мьютекс, если у нас один процесс? Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Вячеслав написал: QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2?
Вы задаёте просто удивительные вопросы, ну вот честно. Ради любопытства, можете сказать, как вы предполагаете использовать 2 мьютекса для синхронизации между потоками? ну вот чисто теоретически? А если это никак - то о чем вопрос??
Понадобится более двух. По одному на доступ к VM каждого из потоков, а также мьютексы для доступа к переменным LUA_REGISTRYINDEX и LUA_GLOBALSINDEX, + по одному мьютексу на каждую таблицу и userdata. Да, возможно было бы глупо с моей стороны считать, что в Quik'е решились на подобный рефакторинг движка Lua.
Нет. Только с библиотеками. Кстати Qt библиотека для Lua тоже есть.
Если в общем, то разработка под Quik под Linux ничем не отличается от разработки под Quik под Windows, только придётся ещё решать проблемы с Wine, если какие-то библиотеки откажутся грузиться.
присоединяюсь к вопросу! За одно вопрос к вам sweg а для Linux есть что-то в виде расширения для Lua , дабы свои окошки рисовать и Linux 'овые функции использовать?
Кхм, в Linux Quik выполняется Wine окружении, если вы знаете, как из Wine-окружения корректно получить доступ к страндартным Linux библиотекам, то и окошки рисовать получится. Сложность здесь в том, что для загрузки Linux-библиотеки функции в WinAPI нет. Кроме того, такое решение будет работать только под Linux. Имхо, лучше уж Qt использовать (для простоты) или DialogBoxParam WinAPI (с самописным кодом обработки оконных сообщений).
QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2? Другими словами, lua_lock / lua_unlock работают с одним и тем же мьютексом, если вызваны из main или из callback'а?
Если 1, то всё становится на свои места: получается, что параллельно никакой Lua-код не выполняется (любая C функция, как то sleep - Lua-кодом не является). Если используются 2 мьютекса, то у меня по-прежнему вопрос, как происходит синхронизация глобальных Lua-переменных при выполнении двух Lua VM (для глобального потока и потока main) в разных системных потоках параллельно?
Подчеркну, что такое знание не может являться "конфеденциальным", т.к. для разработки сложных Lua-плагинов для Quik нужно иметь возможность понимать, как меняются глобальные Lua-переменные из потока main и из главного Lua-потока (для callback'ов).
Вячеслав написал: удостоверился в этом, выведя на печать таблицу LUA_REGISTRYINDEX. LUA_REGISTRYINDEX для потока main хранит объект thread. При этом для главного потока LUA_REGISTRYINDEX в момент инициализации пустая.
Ошибся. LUA_REGISTRYINDEX для state'ов созданных с помощью lua_newthread совпадает с глобальным. Т.о. поток lua_newthread создаётся после интерпретации всего скрипта.
Распечатал адрес lua_State* приходящий в функции при вызове из main и из главного потока. Очень похоже, что главный поток создаётся с помощью lua_State* global = lua_newstate(...); a main - с помощью lua_State *main = lua_newthread(global);
удостоверился в этом, выведя на печать таблицу LUA_REGISTRYINDEX. LUA_REGISTRYINDEX для потока main хранит объект thread. При этом для главного потока LUA_REGISTRYINDEX в момент инициализации пустая.
P.S. Подозреваю, что в QLua просто переопределили lua_lock и lua_unlock, чтобы разрешить многопоточность. Сейчас выясняю, как выполняется синхронизания глобальных переменных в Lua между потоками при переопределённых lua_lock / lua_unlock.
Старатель написал: Позвольте поинтересоваться, зачем переносить обработку в другой поток, если во время обработки основной поток всё равно простаивает? Какая цель преследуется?
К сожалению, не понял вопроса. Схематично у меня 2 потока (callback'и и main) выглядят так. Своих отдельных потоков не создаю.
Код
callback {
найти (связать со сделкой) экземпляр ордера
в случае, если весь объйм транзакции выбран полностью, отписаться в лог и удалить из списка активных транзакций
если выбранный объём изменился или произошла ошибка, отписаться в лог
}
main {
while(running) do
по алгоритму выставить нужные транзакции
по алгоритму выполнять подписку на стаканы нужных инструментов с помощью Subscribe_Level_II_Quotes
раз в 5 секунд {
блокировать список активных транзакций
проверять не истёк ли для них таймаут ожидания ответа (для связки ordernum -- trans_id)
те, для которых истёк, удалять из списка с записью в лог
}
end
}
swerg написал: Вячеслав , то как вы делаете, на мой взгляд в общем-то идеально. Не могу только понять при чем тут версия квика, от неё точно ничего здесь не зависит.
Идеальность в том, что вы, с одной стороны, полностью блокируете работу с таблицей из другого потока на все время добавления в неё элемента, с другой стороны полностью блокируете добавление в таблицу на всё время перебора имеющихся элементов внутри main(). Т.е. у вас всегда гарантированно в цикле обрабатывается некое полностью статичное состояние таблицы. И это само по себе безусловно здорово.
Однако, есть важный момент. Состоит он в том, что на всё время обработки таблицы (в main()) у вас фактически полностью блокирована работа call-back'ов при наступлении события OnParam(). И тут уже возникает вопрос про эффективность такого алгоритма. В самом деле: видно, что вы пытаетесь сделать "очередь событий" в таблице, и события из этой очереди разгребаете как бы в отдельном потоке main(). Но в вашем алгоритме вы не можете положить в таблицу очередное событие из OnParam() до тех пор, пока не обработали все имеющиеся события в таблице. Т.е. у вас получится, что в таблицу попали какие-то события (в виде элементов таблицы), вы их в main() обрабатываете - и на всё время этой обработки у вас OnParam() тупо залочен, QUIK стоит и ждёт, пока вы в main() обработаете все имеющиеся элементы таблицы. Не видно, чтобы тут случился выигрыш-то от двухпоточности. У вас всё равно каждый поток постоянно ждёт другой поток, в том числе и поток добавления событий.
1. Если бы всё было идеально, я бы не спрашивал ответа на форуме. Загвоздка в том, что обычная Lua 5.1 поддерживает работу только в одном потоке, поэтому Quik использует внутри либо одну из многопоточных библиотек Lua (очень похоже на Lua Lanes), либо придерживается своей реализации. В любом случае, проблема в том, что в функции callback'а и функции main используется разные структуры lua_State*, и сразу ли значение, присвоенное в одном lua_State станет доступно в другом - вопрос к команде Quik. Интересно, что адреса всех глобальных таблиц (получаются при вызове tostring(tbl)) в функции main и callback'ах совпадают.
2. В main у меня проход по таблице, если условие для элемента выполнилось, то производится дополнительная работа, но самый максимум получается 20 мс при блокированной таблице (и то раз в 5 секунд), поэтому некритично, если на это время заблокируется главный поток Quik, а так в main я ещё выставляю транзакции (без блокирования чего-либо).
@тех.поддержка: При необходимости, для тестов, могу выслать код библиотеки, которая обслуживает userdata для критических секций. Вопрос по большей части технический. Почти уверен, что так как я делаю, делать можно. Хотелось бы услышать подтверждение от команды Quik'а.
Николай Камынин, вопрос немного не об этом. Я уже выполняю синхронизацию на уровне мьютексов. Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а или наоборот.
Перечисленных потокобезопасных функций мне не достаточно. В частности, мне нужно пройтись по таблице, в которой добавляются/удаляются элементы из callback'а и выполнить определённые действия для некоторых элементов по условию.
Есть код, который выполняется в main и в callback'ах Lua. Задача - обеспечить доступ к разделяемому ресурсу. Допустим, доступ к разделяемому ресурсу осуществляется с помощью C++ std::recursive_mutex или критических секций (WinAPI).
Например, пусть один поток добавляет элемент в таблицу, а другой читает её. Сам вопрос в коде.
Код
-- положим, есть внешняя библиотека, которая создаёт userdata для std::recursive_mutex в lua
-- mtx:lock() вызывает std::recursive_mutex::lock() (или EnterCriticalSection, если используются критические секции вместо recursive_mutex)
-- mtx:unlock() вызывает std::recursive_mutex::unlock() (или LeaveCriticalSection, если --//--)
mtx = recursive_mutex.create();
tbl = {};
function OnParam(class_code, sec_code)
local idx = class_code .. "|" .. sec_code;
mtx:lock();
tbl[idx] = (tbl[idx] or 0) + 1;
mtx:unlock();
end
running = true;
function OnStop()
running = false;
end
function main()
while (running) do
sleep(50);
mtx:lock();
local str = {};
-- ВОПРОС: Будет ли в этом месте таблица tbl содержать актуальные значение?
-- Допустим, только что выполнился вызов mtx:unlock() в OnQuote в callback'е и мы попали сюда в потоке main.
-- Отразится ли изменение выполненное в потоке callback'а строкой кода "tbl[idx] = (tbl[idx] or 0) + 1"
-- на таблице tbl в потоке main?
-- Как добиться здесь актуального значения полей таблицы tbl в этом месте?
for k,v in pairs(tbl) do
str[#str+1] = k .. "=" .. v;
end
message(table.concat(str, "\n"));
mtx:unlock();
end
end
Пример общий. Планируется не только добавлять строки в таблицу, но и удалять, и изменять. И не только в одну таблицу. Прошу дать ответ для последней версии Quik и для версии Quik 6.17.3.6.
Каким образом настроить Quik так, чтобы в случае разрыве связи он автоматически переподключался к серверу? Есть ли возможность инициировать подключение из Lua? Версия Quik 6.17.3.6.
swerg, Такой способ должен быть, т.к. WinAPI поддерживает 3 типа сообщений для каждой кнопки мыши: WM_LBUTTONDOWN WM_LBUTTONUP WM_LBUTTONDBLCLK Первое - это нажатие кнопки, второе - отпускание и третье - двойной клик. В общем, технически это возможно (только важно не забыть установить стиль окна CS_DBLCLKS).
Игорь Князьков, Не рекомендую использовать nil, однако, если кол-во элементов в каждой из строк фиксировано, то это возможно:
Код
t={{1,2,3,4},{5,nil,nil,8},{9,10,11,12}};
columns = 4;
rows = 3;
local f = io.open("myfile.log", "w+");
local temp = {};
for y = 1,rows do
local line = t[y];
for x = 1,columns do
temp[x] = tostring(line[x]);
end
f:write(table.concat(temp, ' '), '\n');
end
f:close();
Для CSV замените ' ' в сткоке f:write... на ',' . Для обработки полей с запятыми в .csv вам нужно будет использовать более сложный алгоритм для их экранизации.
t={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
local f = io.open("myfile.log", "w+");
for _,line in ipairs(t) do
for _,item in ipairs(line) do
f:write(tostring(item), ' ');
end
f:write('\n');
end
f:close();
P.S. или со списком имён, которые больше не будут меняться - это не принципиально.
Главное - дать программисту возможность сконструировать для каждого из пунктов (*) в сообщении выше выражение, которое бы возвращало тип boolean.
(Не важно, как оно будет выглядеть на Lua - важно, чтобы его можно было использовать, а также желательно, чтобы его использование было немного очевидно.)
Вы упорно не хотите принять положение, что теперь некоторые параметры сделки могут измениться.
В этом мнении я полностью солидарен со Старателем. Не апеллирую к nil или 0, однако в коде программисту нужно всегда иметь * возможность идентифицировать, какие поля пришли в OnTrade, а какие нет; * также желательно знать, какие поля ещё придут. * и обязательнознать, какие поля больше не будут меняться.
Иначе программирование сводится к гаданию на кофейной гуще (что же нам пришло и ещё придёт...?), и писать надёжный код, которых не перестанет работать после обновления терминала Quik (про обратную совместимость я вообще молчу).
Организации Arqatech я могу только посоветовать найти нового архитектора ПО (или учредить такую должность, если она отсутствует).
В качестве решения (и пожелания), я бы мог предложить в OnTrade() передавать ещё таблицу со списком имён, которые ещё могут поменяться.
Код
function OnTrade(order)
local pf = order.pending_fields;
-- пример:
-- order.pending_fields = {
-- ["qty"] = true,
-- ["price"] = true
-- };
-- вместо true можно использовать любое другое значение отличное от false, чтобы было удобно проверять наличие поля в хеше:
-- if order.pending_fields["order_num"] then
-- -- .....
-- end
local msg = "Поля: "
for k,_ in pairs(pf) do
msg = msg..tostring(k).." ";
end
msg = msg .. " ожидают изменения".
message(msg);
end
Всё, что вы написали, есть ваша правда. Попрошу вас в дальнейшем быть более точным в высказываниях.
Цитата
Ошибка в том, что переменная order не содержит элемента order_num Поэтому вызов функции onOrder с параметрами или без не спасает от этой ошибки.
Вызов
Код
OnOrder({})
будет вполне нормальным, как и такой код
Код
t = {};
message(tostring(t.order_num));
так как индексация в Lua доступна всегда для таблиц и часто для типа userdata. В остальных случаях вы получите похожую ошибку с указанием типа. Однако, если для типа указана метатаблица, то возможно и его индексирование. Пример:
Код
a = ("test").sub;
message(tostring(a));
Замечу, что указание метатаблицы для типов кроме table и userdata возможно только из Си кода с помощью метода lua_setmetatable и никак из Lua.
Цитата
относительно объявления глобальных переменных в любом месте неверно. вот пример: print(x) x=10 результат nil
В таком примере вы правильно показали, что использование переменной в качестве аргумента функции перед присваиванием ей значения, приводит к ожидаемому результату - она имеет внутри функции значение nil. Но это ли я имел ввиду? Когда по вашему будут использоваться объявления функций? Возьмём такой код.
Код
function main()
test1();
end
function test1()
test2();
end
function test2()
message("Test message");
end
Здесь, например, функция test2 используется перед её объявлением. Однако такой код работает, и происходит это потому, что функция main вызывается (из Си кода терминала Quik) после интерпретации всего Lua-файла.
Даже такой код будет работать с тем же эффектом:
Код
function main()
test2 = function()
message("Test message");
end
test1();
end
function test1()
test2();
end
Для тех, кому интересно как работает Lua снаружи и изнутри, рекомендую книгу Roberto Ierusalimschy "Programming in Lua" (2013)
--этот кусок кода не исполняется if (curhms==oldt_si and curhms==oldt_eu and data.eq==0 and GetEquity==true) then
Подозреваю, что ошибка в выделенной части. Т.к. условие curhms==oldt_si and curhms==oldt_eu выполнится только при равенстве трёх переменных curhms, oldt_si, oldt_eu. Это ли имелось ввиду? Кроме того, строка кода выше
Цитата
if (curhms==oldt_si and curhms==oldt_eu ) then return end
просто выходит из функции. Получается код, который не выполняется, действительно недостижим, т.к. приведённые условия совпадают.
означает, что индексация через точку локальной переменной order (в вашем случае -- параметра функции) невожможна, потому что order имеет значение nil. Происходит это потому, что вы вызываете OnOrder() из main без параметров. Lua никак не контролирует число параметров при вызове функций: если их меньше чем нужно, то недостающие примут значение nil (так, например, параметр order функции OnOrder принимает значение nil при вызове OnOrder() в вашем примере), если их больше чем нужно, то лишние параметры игнорируются. После такой ошибки происходит генерация исключения в lua и, выход из кода функции OnOrder, а затем и из main, т.к. код по перехвату исключений у вас отсутствует. После выхода из main (любого: с ошибкой или без) происходит выгрузка скрипта.
Ваша ошибка заключается в том, что вы вызываете OnOrder без параметров из main. OnOrder вызывается терминалом при регистрации заявки. Вызывать её явно, обычно, не имеет смысла. Советую также обратить внимание на то, что код в main и код callback'ов OnOrder, OnTrade... выполняется параллельно. Этот факт обязательно нужно учитывать при передаче параметров через глобальные переменные из callback'ов в main и обратно. Николай Камынин,
Цитата
А ошибку Вы получаете потому, что у Вас вызов функции происходит до ее описания Попробуйте изучить Lua.
Пожалуйста, не надо вводить человека в заблуждение. Объявление глобальных функций в Lua возможно в любом порядке, т.к. после загрузки скрипта сначала производится его выполнение (выполняется весь код вне функций и объявление функций), и лишь затем терминалом вызывается main и остальные callback'и. Как, например, OnOrder в примере кода выше.
_sk_, если будет время, пожалуйста, посмотрите на следующий скрипт. Он пока только умеет выставлять и отслеживать limit-заявки (т.е. без снятия/изменения). Хотелось бы понять, в правильном ли направлении я двигаюсь.
Пока не смог разобраться с пунктами
Цитата
3) Нужно следить за UID экземпляра QUIK, чтобы фильтровать "чужие" заявки.
Как получить свой uid? Что делать, если OnTransReply / OnOrder / OnStopOrder пришёл с другим uid? Правильно ли я понял, что под UID имеется ввиду поле uid, приходящее в OnTransReply и OnOrder?
Сам скрипт:
Код
Multimap = {};
Multimap.__index = Multimap;
function Multimap.Create()
local self = {};
setmetatable(self, Multimap);
return self;
end
function Multimap:Ins ert(key, value)
local list = self[key];
if (list == nil) then
list = {};
self[key] = list;
end
table.ins ert(list, value);
end
function Multimap:Remove(key, value)
local list = self[key];
assert(list ~= nil, "key not found");
for k,v in ipairs(list) do
if (v == value) then
table.remove(list, k);
if (#list == 0) then
self[key] = nil;
end
return;
end
end
assert(false, "val ue not found");
end
-- Transaction class
Transaction = {};
Transaction.__index = Transaction;
ST_PENDING_NEW = 1;
ST_REJECTED = 2;
ST_NEW = 3;
ST_FILLED = 4;
function Transaction.CreateLimit(request)
local self = {};
setmetatable(self, Transaction);
self.request = request;
local os_time = os.time();
self.id = self:GenerateId(os_time);
self.time = os_time;
self:ModifyHeader();
self.status = ST_PENDING_NEW;
Robot.transid_map[self.id] = self;
self.volume = tonumber(self.request.QUANTITY);
self.pending_volume = self.volume;
self.trades = {};
local ret = sendTransaction(self.request);
if (ret ~= '') then
Robot.transid_map[self.id] = nil;
self:OnStatusChanged(ST_REJECTED);
else
Robot.time_map:Insert(os_time, self);
end
end
function Transaction:ModifyHeader(os_time)
assert(self.request.TRANS_ID == nil, "We use trans_id internally.");
self.request.TRANS_ID = string.format("%d", self.id);
self.request.CLIENT_CODE = string.format("%s//L%04d", self.request.CLIENT_CODE, Robot.id);
end
Transaction.max_trans_id = 0;
function Transaction:GenerateId(os_time)
Transaction.max_trans_id = Transaction.max_trans_id + 1;
local id = Robot.id * 10000 + os_time % (60*60*24*30 * 300) + Transaction.max_trans_id;
assert(Robot.transid_map[id] == nil);
return id;
end
function Transaction:UpdateTiming()
Robot.time_map:Remove(self.time, self);
local os_time = os.time();
self.time = os_time;
Robot.time_map:Insert(os_time, self);
end
function Transaction:InternalDelete()
Robot.time_map:Remove(self.time, self);
Robot.transid_map[self.id] = nil;
if (self.orderid ~= nil) then
Robot.orderid_map[self.orderid] = nil;
end
end
-- consider we matched transaction by id and client_code already
function Transaction:OnTransReply(trans_reply)
if (self.flag_ontransreply) then
-- ignore all callback duplicates
return;
end
assert(trans_reply.class_code == self.request.CLASSCODE and trans_reply.sec_code == self.request.SECCODE, "classcode or seccode mismatch");
self.flag_ontransreply = true;
-- только status == 3 говорит об успешном выставлении заявки
if (trans_reply.status == 3) then
self:OnStatusChanged(ST_NEW);
if (self.orderid == nil) then
self.orderid = trans_reply.order_num;
Robot.orderid_map[self.orderid] = self;
else
assert(self.orderid == trans_reply.order_num);
end
self:UpdateTiming();
else
self:OnStatusChanged(ST_REJECTED);
self:InternalDelete();
end
end
-- consider we matched transaction by id and client_code already
function Transaction:OnOrder(order)
if (self.flag_onorder) then
-- ignore all callback duplicates
return;
end
assert(order.class_code == self.request.CLASSCODE and order.sec_code == self.request.SECCODE, "classcode or seccode mismatch");
self.flag_onorder = true;
if (self.orderid == nil) then
self.orderid = trans_reply.order_num;
Robot.orderid_map[self.orderid] = self;
else
assert(self.orderid == order.order_num);
end
local test = bittest(order.flags, 3);
if (test == 0) then
-- если бит 0 и 1 не установлены -- заявка выполнена
self:OnStatusChanged(ST_FILLED);
self:InternalDelete();
else
local sell = bittest(order.flags, 4) ~= 0;
assert((sell == (self.request.OPERATION == "S")) and (not sell == (self.request.OPERATION == "B")));
if (test == 2) then
self:OnStatusChanged(ST_REJECTED);
self:InternalDelete();
else
self:UpdateTiming();
end
end
end
-- consider we matched transaction by id and client_code already
function Transaction:OnTrade(order)
if (self.trades[order.trade_num]) then
-- ignore all callback duplicates
return;
end
assert(order.class_code == self.request.CLASSCODE and order.sec_code == self.request.SECCODE, "classcode or seccode mismatch");
self.trades[order.trade_num] = true;
print(string.format("OnTrade: %s:%s qty %g price %g settle %s:%s:%d",
order.class_code, order.sec_code, order.qty, order.price, order.settle_currency, order.settlecode, order.settle_date));
self.pending_volume = self.pending_volume - order.qty;
assert(self.pending_volume >= 0);
self:UpdateTiming();
end
function Transaction:OnStatusChanged(new_status)
message("Transaction changed status fr om "..self.status.." to "..new_status, 1);
self.status = new_status;
end
function Transaction:OnTimeout()
if (self.pending_volume == 0) then
self:InternalDelete();
else
self:UpdateTiming();
end
end
-- Robot class
Robot = {};
Robot.__index = {};
function Robot.Create(id, timeout)
assert(id ~= nil, "id is required");
timeout = timeout or 5;
assert(Robot.id == nil, "Robot is singleton class");
Robot.id = id;
Robot.timeout = timeout;
Robot.time_map = Multimap.Create();
Robot.transid_map = {};
Robot.orderid_map = {};
Robot.trade_queue = Multimap.Create();
return Robot;
end
function Robot:CheckTimeouts()
local limit_time = os.time() - Robot.timeout;
for timestamp,list in pairs(Robot.time_map) do
if (timestamp <= limit_time) then
for _,trans in ipairs(list) do
trans:OnTimeout();
end
end
end
-- check not matched yet transactions
for orderid,list in pairs(self.trade_queue) do
local trans = self.orderid_map[orderid];
if (trans ~= nil) then
self.trade_queue[orderid] = nil;
for _,v in ipairs(list) do
trans:OnTrade(v);
end
end
end
end
function Robot:CheckTradeQueueFor(trans)
local list = self.trade_queue[trans.orderid];
if (list ~= nil) then
self.trade_queue[trans.orderid] = nil;
for _,v in pairs(list) do
trans:OnTrade(v);
end
end
end
function Robot:CheckComment(comment)
return tonumber(string.match(comment, ".+//L(%d%d%d%d)$")) == self.id;
end
function Robot:SearchTransaction(transid, comment)
if comment and not self:CheckComment(comment) then
return nil;
end
if transid then
return self.transid_map[transid];
end
return nil;
end
function OnTransReply(trans_reply)
local trans = Robot:SearchTransaction(trans_reply.trans_id, nil, trans_reply.brokerref);
if (trans ~= nil) then
trans:OnTransReply(trans_reply);
end
end
function OnTrade(order)
if Robot:CheckComment(order.brokerref) then
local trans = Robot.orderid_map[order.order_num];
if (trans ~= nil) then
Robot:CheckTradeQueueFor(trans);
trans:OnTrade(order);
else
Robot.trade_queue:Insert(order.order_num, order);
end
end
end
function OnOrder(order)
local trans = Robot:SearchTransaction(order.trans_id, order.brokerref);
if (trans ~= nil) then
trans:OnOrder(order);
end
end
running = true;
function OnStop()
running = false;
end
function main()
print("Main function execution");
local robot = Robot.Create(4242);
Transaction.CreateLim it {
ACCOUNT = "L01-00000F00",
CLIENT_CODE = "51448",
ACTION = "NEW_ORDER",
CLASSCODE = "TQBR", SECCODE = "MTSS",
OPERATION = "S",
TYPE = "M", PRICE = "0", QUANTITY = "1",
};
while running do
sleep(50);
robot:CheckTimeouts();
end
end
-- Utilities
function bittest(val ue, mask)
local v = 1;
local v2 = 2;
local res = 0;
for i = 1,32 do
if (v > mask) then
break;
end
local m = mask % v2 / v >= 1;
local s = val ue % v2 / v >= 1;
if (m and s) then
res = res + v1;
end
v = v2;
v2 = v2 * 2;
end
return res;
end
log_file = io.open(getScriptPath().."\\robot.log", "a");
log_file:write("\n");
function print(...)
local r = { '[', os.date(), ']' };
local first = true;
for _,v in pairs({...}) do
if (first) then first = false; else r[#r+1] = '\t'; end
r[#r+1] = tostring(v);
end
r[#r+1] = '\n';
log_file:write(table.concat(r));
end
Ответы на многие вопросы проясняться, если Вы представите себе, что с помощью sendTransaction() с цикле отправляются несколько заявок по нескольким счетам из нескольких разных скриптов одновременно, после чего каждый скрипт начинает получать в произвольном порядке коллбэки, причём каждый скрипт получает коллбэки от всего множества отправленных заявок. Получится, что номера счетов могут быть разными, коды инструментов могут быть разными, какие-то заявки "свои для скрипта", а какие-то "чужие".
Как раз вопрос мой про это. Как отследить выполнение одной транзакции. В приведённом коде я уже выполняю необходимые проверки и от команды QUIK мне хотелось бы знать, достаточны ли они. Код заточен под то, что callback'и для одной транзакции приходят в порядке OnTransReply -> OnTrade -> OnOrder.
Может ли их порядок меняться местами - это тоже вопрос к команде QUIK. Про то, что OnOrder приходит дважды - я знаю, притом в таблице order поля не меняются. С двойным OnTrade не встречался, он приходит с разными таблицами order?
И если не придёт OnTransReply, разве не должен sendTransaction вернуть в данном случае ошибку?
Про ассинхронность - отдельный разговор. В приведённом мной примере только код после вызова sendTransaction в ф-ции main может выполняться параллельно. Всё остальное - синхронно, т.к. выполняется в главном потоке QUIK.
Жаль в документации нет этой информации по отслеживанию транзакций, поэтому и спрашиваю на форуме. В идеале хотелось бы создать Lua script, который генерирует одну или несколько транзакций и следит за их выполнением, обрабатывая ошибочные ситуации. Без участия команды QUIK эту проблему не решить, чтобы хоть как-то гарантировать, чтобы такой базовый робот продолжал работать и отчитываться об ошибках в течение дня и даже после обновления версии терминала.
Пожалуйста, помогите удостоверится в правильности отслеживания выполнения транзакций в QUIK. Вопросов у меня много по процессу. Хотелось бы получить ответы на все из них.
Ниже код с вопросами:
Код
-- Utility. Not optimized. Just an example.
function bittest(number, shift)
local v = 1;
for i=1,shift do
v = v * 2;
end
return (number - v) ~= number;
end
running = true;
transaction = {}
transaction_stage = "not send";
transaction_details = {};
function set_stage(new_stage)
transaction_stage = new_stage;
message(transaction_stage, 1);
end
function OnStop()
running = false;
end
function main()
transaction = {
ACCOUNT = "L01-00000***", -- (*** - для приватности)
CLIENT_CODE = "51***//COMMENT", -- лимит 20 символов для этого поля, это верно для транзаций по всем инструментам?
ACTION = "NEW_ORDER",
CLASSCODE = "TQBR", SECCODE = "MTSS",
OPERATION = "B",
TYPE = "M", PRICE = "0", QUANTITY = "1",
TRANS_ID = "1000001", -- какие есть ограничения на значения этого поля? (самые жёсткие, для всех классов инструментов сразу)
};
local result = sendTransaction(transaction);
if (result ~= '') then
message("Ошибка отправки транзакции: "..result, 1);
else
transaction_stage = "send";
end
while running do sleep(50); end
end
function OnTransReply(trans_reply)
if (tostring(trans_reply.trans_id) == transaction.TRANS_ID) then
if (trans_reply.brokerref ~= transaction.CLIENT_CODE) then
-- Возможна ли такая ситуация, и в каком состоянии находится транзация?
set_stage("???"); -- прошу уточнить, что должно быть вместо ??? (в каком состоянии транзакция)
return;
end
if (trans_reply.class_code ~= transaction.CLASSCODE or trans_reply.sec_code ~= transaction.SECCODE) then
-- Возможна ли такая ситуация, и в каком состоянии находится транзация?
set_stage("???");
return;
end
if (trans_reply.quantity ~= tonumber(transaction.QUANTITY)) then
-- Возможна ли такая ситуация, и в каком состоянии находится транзация?
-- Может ли транзакция выполниться частично? При каких условиях?
set_stage("???");
return;
end
-- какие коды (ret_code) могут быть в result_msg? где можно посмотреть весь список?
local ret_code = string.match(trans_reply.result_msg, "^%((%d+)%)");
-- в чём измеряется time? как привести время к текущему?
local time = trans_reply.time;
if (trans_reply.status == 3) then
transaction_details.order_num = trans_reply.order_num;
set_stage("Транзакция успешно отправлена на сервер в "..time.." с кодом сообщения "..ret_code);
else
set_stage("Ошибка отправки транзакции на сервер (время "..time..", код "..ret_code..")");
end
end
end
function OnTrade(order)
if (transaction_details.order_num == order.order_num) then
-- пожалуйста, прокомментируйте возможные значащие значения order.flags в этом месте
if (order.account ~= transaction.ACCOUNT) then
-- Возможна ли такая ситуация, и в каком состоянии находится сделка?
set_stage("???");
return;
end
if (order.brokerref ~= transaction.CLIENT_CODE) then
-- Возможна ли такая ситуация, и в каком состоянии находится сделка?
set_stage("???");
return;
end
if (order.class_code ~= transaction.CLASSCODE or order.sec_code ~= transaction.SECCODE) then
-- Возможна ли такая ситуация, и в каком состоянии находится сделка?
set_stage("???");
return;
end
if (order.qty ~= tonumber(transaction.QUANTITY)) then
-- Возможна ли такая ситуация, и как будет выглядеть частичное выполнение транзакции на максимально доступный объём?
set_stage("???");
return;
end
transaction_details.price = order.price;
-- где ещё может использоваться trade_num в дальнейшем? при каких условиях? (пока не встречал этот id вне метода OnTrade)
transaction_details.trade_num = order.trade_num;
-- правильный ли тут статус?
set_stage(string.format("Совершается сделка %d на %s лотов %d по цене %s",
order.trade_num, bittest(order.flags, 2) and "продажу" or "покупку", order.qty, order.price))
end
end
function OnOrder(order)
if (transaction_details.order_num == order.order_num) then
-- те же проверки, что и в OnTrade
if (order.account ~= transaction.ACCOUNT) then
-- Возможна ли такая ситуация, и в каком состоянии находится сделка?
set_stage("???");
return;
end
if (order.brokerref ~= transaction.CLIENT_CODE) then
-- Возможна ли такая ситуация, и в каком состоянии находится сделка?
set_stage("???");
return;
end
if (order.class_code ~= transaction.CLASSCODE or order.sec_code ~= transaction.SECCODE) then
-- Возможна ли такая ситуация, и в каком состоянии находится сделка?
set_stage("???");
return;
end
if (order.qty ~= tonumber(transaction.QUANTITY)) then
-- Возможна ли такая ситуация, и как будет выглядеть частичное выполнение транзакции на максимально доступный объём?
set_stage("???");
return;
end
-- какие биты в order.flags в этом месте являются значащими?
-- что значит в этом месте установленный бит 4 в order.flags? (бит 4 (0x10) Разрешить / запретить сделки по разным ценам)
-- правильный ли тут статус?
set_stage(string.format("Заявка %d на %s лотов %d выполнена",
order.order_num, bittest(order.flags, 2) and "продажу" or "покупку", order.qty))
end
end