Вячеслав + (Все сообщения пользователя)

Выбрать дату в календареВыбрать дату в календаре

Страницы: Пред. 1 2
std::recursive_mutex и cинхронизация потоков в Lua
 
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.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
swerg написал:
И да, код в смысле Lua-программы прерывается в реализованном случае вовсе не обязательно "между операторами Lua". Прерваться код может и "посередине выполнения одного из операторов".
Да, выполнение любого потока может прерваться в любом месте. Что я имею ввиду, так это то, что 2 Lua-команды (из потока main и потока callback'а) одновременно выполняться не могут: если выполнение потока main будет приостановлено системным планировщиком на середине выполнения Lua-команды (luaV_execute) и в этот момент начнёт выполняться основной поток Quik, который захочет вызвать наш callback, то основной поток Quik будет ждать на lua_lock операции (планировщик переключит выполнение на другой поток вместо основного потока Quik), т. к. поток main в данный момент владеет мьютексом, который предотвращает одновременное выполнение двух Lua-комманд из разных потоков.
std::recursive_mutex и cинхронизация потоков в Lua
 
тот самый,
Положим, я называю критические секции мьютексами.
В чём заключается Ваше предложение?
Как принудительно остановить скрипт qlua программно из скрипта до окончания функции main?
 
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 таким же образом получает сообщение об ошибке в нашем скрипте.
std::recursive_mutex и cинхронизация потоков в Lua
 
swerg, проверил. Подтверждаю слова Sergey Gorokhov.

Предполагаю, что 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
Когда имеет смысл использовать .bin файл ? И как их использовать ?
 
Ярослав С, по моему опыту, хранить всё равно в каком формате, т.к. загрузка / сохранение выполняться будут редко. Кроме того, привязка к какому-нибудь самописному бинарному формату потребует поддержки в будущем.

Рекомендую вот этот пример
http://lua-users.org/wiki/SaveTableToFile
Все нужные параметры сохраните в таблицу и затем сохраняйте/загружайте её. Формат сохранения -- lua-файл -- будет очевиден после возвращения к разрабатываемому коду.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
Николай Камынин написал:
Цитата
Вячеслав   написал:
Цитата
Николай  Камынин   написал:
У меня вопрос к автору темы.
А зачем использовать мьютекс, если у нас один процесс?
Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
Я собственно спросил зачем мьютекс если один процесс,
в том плане,
что в одном процессе синхронизацию потоков эффективнее делать другими средствами
либо в пользовательском режиме -атомарным операциями и критическими секциями
либо ядерными - событиями.
Это быстрее , чем мьютекс.
Я не понял контекст вашего оригинального сообщения. Есть просто термин мьютекс, есть std::mutex (или std::recursive_mutex - по сути реализованые через аналог критических секций на уровне Concurrency namespace'а в MS CRT), и есть WinAPI mutex, который может использоваться несколькими процессами сразу. В первом сообщении я сразу упомянул, что имею ввиду std::recursive_mutex или критические секции, а дальше уже оба варианта называл просто "мьютексом", чтобы не писать одно и то же по нескольку раз.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
Sergey Gorokhov написал:
Цитата
Вячеслав   написал:
Поэтому же Quik можно подвесить простым кодомКодwhile (true) do end
Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback.
В результате, Quik зависнит
нет не зависнет
Тогда в какой момент произойдёт переключение выполнения потока main на выполнение callback'а? Ведь в main таким кодом мы монопольно занимаем Lua VM.
std::recursive_mutex и cинхронизация потоков в Lua
 
swerg,
Подытожу: в Lua переключение потоков (глобального на main и обратно) происходит только в момент вызова любой Си функции (любой библиотечной функции). В остальное время, если Lua-код выполняется, то он выполняется атомарно. Поэтому же Quik можно подвесить простым кодом
Код
while (true) do end
Поставив его, например, в main и пусть в этот момент Quik захочет вызвать какой-нибудь callback.
В результате, Quik зависнит на lua_lock Си функции, т.к. Lua VM будет в этот момент занята потоком main, выполняющим наш бесконечный цикл.

Я прав?
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
Николай Камынин написал:
У меня вопрос к автору темы.
А зачем использовать мьютекс, если у нас один процесс?
Спасибо
Даже не знаю с чего начать. В Windows "процесс" - это контейнер, в котором выполняются потоки (нити). Потоки имеют состояние выполнения, которое включает регистры процессора и стек. Адресное пространство (память) у потоков одного процесса общая. Для синхронизации доступа к ресурсам, которые используются несколькими потоками используют мьютексы.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
swerg написал:
Вы так пишете, как-будто есть другие способы.
Про это уже писалось (на старом форуме, как водится).
Меня не было на старом форуме, поэтому не знаю.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
swerg написал:
Цитата
Вячеслав   написал:
QLua использует 1 мьютекс для разделения выполнения функции main и callback'ов или 2?
Вы задаёте просто удивительные вопросы, ну вот честно.
Ради любопытства, можете сказать, как вы предполагаете использовать 2 мьютекса для синхронизации между потоками? ну вот чисто теоретически?
А если это никак - то о чем вопрос??
Понадобится более двух. По одному на доступ к VM каждого из потоков, а также мьютексы для доступа к переменным LUA_REGISTRYINDEX и LUA_GLOBALSINDEX, + по одному мьютексу на каждую таблицу и userdata. Да, возможно было бы глупо с моей стороны считать, что в Quik'е решились на подобный рефакторинг движка Lua.  
Переподключение к серверу: автоматическое или из Lua
 
Цитата
Валентин написал:
Цитата
swerg   написал:
После потери связи можно настроить автопереподключение штатными средствами.
А чтобы терминал   автологинился при запуске (или перезапуске в случае краха) можно использовать скрипт на Lua с небольшой доп. библиотекой  .
пользуюсь этой штукой, если котлету и потрепали мошенники, то не сильно
Вещь хорошая, но она перестанет работать, если в Quik'е с обновлением изменят окно ввода пользователя и пароля.
Переподключение к серверу: автоматическое или из Lua
 
Цитата
Николай Бехтерев написал:
Цитата
   s_mike@rambler.ru  написал:
DialogBoxParam WinAPI
в чистом луа или и под wine тоже?
Нет. Только с библиотеками. Кстати Qt библиотека для Lua тоже есть.

Если в общем, то разработка под Quik под Linux ничем не отличается от разработки под Quik под Windows, только придётся ещё решать проблемы с Wine, если какие-то библиотеки откажутся грузиться.
std::recursive_mutex и cинхронизация потоков в Lua
 
Подозреваю, что используется всё же 1 мьютекс, если даже Roberto Ierusalimschy об этом заявляет. http://lua-users.org/lists/lua-l/2011-05/msg00172.html
Переподключение к серверу: автоматическое или из Lua
 
Цитата
Николай Бехтерев написал:
Цитата
swerg   написал:
Научите без библиотек?
присоединяюсь к вопросу!
За одно вопрос к вам  sweg  а для  Linux  есть что-то в виде расширения для  Lua , дабы свои окошки рисовать и  Linux 'овые функции использовать?
Кхм, в Linux Quik выполняется Wine окружении, если вы знаете, как из Wine-окружения корректно получить доступ к страндартным Linux библиотекам, то и окошки рисовать получится. Сложность здесь в том, что для загрузки Linux-библиотеки функции в WinAPI нет. Кроме того, такое решение будет работать только под Linux.
Имхо, лучше уж Qt использовать (для простоты) или DialogBoxParam WinAPI (с самописным кодом обработки оконных сообщений).
std::recursive_mutex и cинхронизация потоков в Lua
 
Вопрос к техподдержке:

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'ов).
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
Вячеслав написал:
удостоверился в этом, выведя на печать таблицу LUA_REGISTRYINDEX.
LUA_REGISTRYINDEX для потока main хранит объект thread. При этом для главного потока LUA_REGISTRYINDEX в момент инициализации пустая.
Ошибся. LUA_REGISTRYINDEX для state'ов созданных с помощью lua_newthread совпадает с глобальным. Т.о. поток lua_newthread создаётся после интерпретации всего скрипта.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
swerg написал:
Это вот откуда такой вывод?
Распечатал адрес 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.
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
Старатель написал:
Позвольте поинтересоваться, зачем переносить обработку в другой поток, если во время обработки основной поток всё равно простаивает? Какая цель преследуется?

К сожалению, не понял вопроса. Схематично у меня 2 потока (callback'и и main) выглядят так. Своих отдельных потоков не создаю.

Код
callback {
    найти (связать со сделкой) экземпляр ордера
    в случае, если весь объйм транзакции выбран полностью, отписаться в лог и удалить из списка активных транзакций
    если выбранный объём изменился или произошла ошибка, отписаться в лог
}

main {
    while(running) do
        по алгоритму выставить нужные транзакции
        по алгоритму выполнять подписку на стаканы нужных инструментов с помощью Subscribe_Level_II_Quotes
        раз в 5 секунд {
            блокировать список активных транзакций
            проверять не истёк ли для них таймаут ожидания ответа (для связки ordernum -- trans_id)
           те, для которых истёк, удалять из списка с записью в лог
        }
    end
}
std::recursive_mutex и cинхронизация потоков в Lua
 
Цитата
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 я ещё выставляю транзакции (без блокирования чего-либо).
std::recursive_mutex и cинхронизация потоков в Lua
 
@тех.поддержка: При необходимости, для тестов, могу
выслать код библиотеки, которая обслуживает userdata для критических секций.
Вопрос по большей части технический. Почти уверен, что так как я делаю, делать можно.
Хотелось бы услышать подтверждение от команды Quik'а.
std::recursive_mutex и cинхронизация потоков в Lua
 
Николай Камынин, вопрос немного не об этом.
Я уже выполняю синхронизацию на уровне мьютексов.
Вопрос в том, прочитаются ли данные в потоке main сразу после вставки их в таблицу в обработчике callback'а или наоборот.

Перечисленных потокобезопасных функций мне не достаточно.
В частности, мне нужно пройтись по таблице, в которой добавляются/удаляются элементы из callback'а и выполнить
определённые действия для некоторых элементов по условию.
Как определить используемую версию версию синтаксиса lua в терминале ?
 
Денис Зямаев,
Код
message(_VERSION);
std::recursive_mutex и cинхронизация потоков в Lua
 
Есть код, который выполняется в 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.
Переподключение к серверу: автоматическое или из Lua
 
Каким образом настроить Quik так, чтобы в случае разрыве связи он автоматически переподключался к серверу? Есть ли возможность инициировать подключение из Lua? Версия Quik 6.17.3.6.
SetTableNotificationCallback, множественное срабатывание
 
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 вам нужно будет использовать более сложный алгоритм для их экранизации.
Вывод таблицы
 
лучше, конечно, файл открывать так (с указанием папки скрипта):
Код
local f = io.open(getScriptPath().."\\myfile.log", "w+");
Вывод таблицы
 
Код
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();
ошибка транзакции
 
Можете добавить эту строчку в любое место. И затем посмотрите, что выводится в сообщении.
ошибка транзакции
 
Что показывает
Код
message(tostring(Price).." ("..tostring(type(Price))..")");
QUIK (версия 7.0.1.5), function OnTrade(trade), трехкратный вызов на одно событие.
 
P.S. или со списком имён, которые больше не будут меняться - это не принципиально.

Главное - дать программисту возможность сконструировать для каждого из пунктов (*) в сообщении выше выражение, которое бы возвращало тип boolean.

(Не важно, как оно будет выглядеть на Lua - важно, чтобы его можно было использовать, а также желательно, чтобы его использование было немного очевидно.)
QUIK (версия 7.0.1.5), function OnTrade(trade), трехкратный вызов на одно событие.
 
Цитата
Вы упорно не хотите принять положение, что теперь некоторые параметры сделки могут измениться.
В этом мнении я полностью солидарен со Старателем.
Не апеллирую к 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


 
OnOrder выдает ошибку
 
Уважаемый,
Цитата
Все что я написал - есть правда.
Всё, что вы написали, есть ваша правда. Попрошу вас в дальнейшем быть более точным в высказываниях.
Цитата
Ошибка в том, что переменная 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)
Синхронизация данных от SetUpdateCallback
 
Максим,
Цитата
--этот кусок кода не исполняется
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
просто выходит из функции. Получается код, который не выполняется, действительно недостижим, т.к. приведённые условия совпадают.
Как бороться с файлом alltrade.dat
 
Николай Камынин,
Цитата
автоматом: 2)  батник прописать сценарий удалить файлы 3)  п 1 сделать автоматом 4)  написать  скрипт в батник для запуска квика и прочего.
Можете поделиться решением?
OnOrder выдает ошибку
 
yves1,
Ошибка, которую вы получили
Цитата
attempt to index local 'order' (a nil value)
означает, что индексация через точку локальной переменной 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 в примере кода выше.
Работа с OnOrder
 
bulat,
Код
transactions = {};

function OnOrder(order)
    if (transactions[order.order_num]) then
        return;
    end
    transactions[order.order_num] = true;

    -- .....
end
Порядок отслеживания процесса выполнения транзакций
 
swerg, прошу прощения, забыл уточнить.
Цитата
Как получить свой UID из Lua?
Этот вопрос уже решил.
Код
GetInfoParam("USERID")
Порядок отслеживания процесса выполнения транзакций
 
_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 эту проблему не решить, чтобы хоть как-то гарантировать, чтобы такой базовый робот продолжал работать и отчитываться об ошибках в течение дня и даже после обновления версии терминала.
Порядок отслеживания процесса выполнения транзакций
 
Прошу простить. Код для bittest в предыдущем сообщении оказался неверен. Вот правильный вариант.
Код
function bittest(number, shift)
    local v = 1;
    for i=1,shift do
        v = v * 2;
    end
    return number / v % 2 >= 1;
end
Порядок отслеживания процесса выполнения транзакций
 
Пожалуйста, помогите удостоверится в правильности отслеживания выполнения транзакций в 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

Страницы: Пред. 1 2
Наверх