1

Тема: СКРИПТ "ХРАНИТЕЛЬ СДЕЛОК"

Как-то нарыл один почти простой скрипт, который выводит совершённые сделки в экселевский файл.
Всё бы ничего, но ведёт он себя оч. странно. Обращался к его же создателю, но ответ получился невразумительным, он не знает что c ним произошло или что произошло с обновлениями Quik-а, раз вылазит такая хрень.
Если в терминале ещё не было сделок, то скрипт стартует без помех.
https://quik2dde.ru/static-img/359/d9814260584d.jpg
Стоит только совершить сделку или несколько, сразу выскакивает окошко с ошибкой,
которое сигнализирует о неисправности скрипта,
но скрипт продолжает дальше работать.
https://quik2dde.ru/static-img/359/2d696870c9d4.jpg
https://quik2dde.ru/static-img/359/e51e4ec3893e.jpg
Это выход из сделки,
всё так же, ошибка и работающий скрипт
https://quik2dde.ru/static-img/359/6641dbd4f1c0.jpg
Если теперь скрипт остановить и попытаться запустить по новой,
то он уже не запуститься и будет выдавать всё ту же ошибку
https://quik2dde.ru/static-img/359/88f7f7f64b18.jpg

По идее всё должно работать, но он не работает. Подскажите где закралась ошибка в коде.

Run = true; -- флаг работы цикла в main
 
DataFolder = ''; -- Полный путь к папке "Данные(c)quikluacsharp.ru"
TradesFiles = {};-- Массив дескрипторов файлов
 
function OnInit()
   -- Получает полный путь к папке "Данные(c)quikluacsharp.ru"
   DataFolder = getWorkingFolder()..'\\Данные(c)quikluacsharp.ru\\';
   -- Создает папки по всем найденным счетам
   CreateAccountsFolders();
   -- Записывает все ранее не записанные сделки из таблицы "Сделки" в файлы
   CheckAndSaveTerminalTrades();
end;
 
function main()
   while Run do      
      sleep(1);
   end;   
end;
 
-- Создает каталоги по всем найденным счетам
function CreateAccountsFolders()
   -- Перебирает все счета
   for i=0, getNumberOf("trade_accounts")-1 do
      -- Получает номер счета
      local Account = getItem("trade_accounts", i).trdaccid;
      -- Получает путь
      local Path = '"'..DataFolder..Account..'\\"';
      -- Если каталог не существует
      if os.execute('cd '..Path) == 1 then
         -- Создает каталог
         os.execute('mkdir '..Path); 
      end;
   end;
end;
 
-- Проверяет записана ли данная сделка в файл истории
function CheckTradeInFile(trade)
   -- Получает путь к файлу инструмента в папке торгового счета
   local PathAccountSec = DataFolder..trade.account..'\\'..trade.sec_code..'.csv';
   -- Пытается открыть файл текущего инструмента в режиме "чтения"
   local TradesFile = io.open(PathAccountSec,"r");
   -- Если файл не существует, то сделка не записана
   if TradesFile == nil then return false;
   else -- Если файл существует
      -- Получает индекс файла
      local FileIndex = trade.account..'_'..trade.sec_code;
      -- Если файл еще не открыт для дописывания
      if TradesFiles[FileIndex] == nil then
         -- Открывает файл текущего инструмента в режиме "дописывания"
         TradesFiles[FileIndex] = io.open(PathAccountSec,"a+");
      end;
      -- Перебирает строки файла
      local Count = 0; -- Счетчик строк
      for line in TradesFile:lines() do
         Count = Count + 1;
         if Count > 1 and line ~= "" then
            -- Если номера сделок совпадают, то сделка записана
            local i = 0;
            for str in line:gmatch("[^;^\n]+") do
               i = i + 1;
               if i == 3 and tonumber(str) == trade.trade_num then
                  TradesFile:close();
                  return true; 
               end;
            end;
         end;      
      end;
   end;
   TradesFile:close();
   return false;
end;
-- Записывает все ранее не записанные сделки из таблицы "Сделки" в файлы
function CheckAndSaveTerminalTrades()
   local trade = nil;
   -- Перебирает все сделки в таблице "Сделки"
   for i=0,getNumberOf("trades")-1,1 do      
      trade = getItem ("trades", i);
      -- Если данная сделка еще не записана в файл истории
      if not CheckTradeInFile(trade) then        
         -- Добавляет сделку в файл истории
         AddTradeInFile(trade);
      end;
   end;
end;
-- Добавляет новую сделку в файл истории
function AddTradeInFile(trade)
   local DateTime = trade.datetime;
   local Date = tonumber(DateTime.year);
   local month = tostring(DateTime.month);
   if #month == 1 then Date = Date.."0"..month; else Date = Date..month; end;
   local day = tostring(DateTime.day);
   if #day == 1 then Date = Date.."0"..day; else Date = Date..day; end;
   Date = tonumber(Date);
   local Time = "";
   local hour = tostring(DateTime.hour);
   if #hour == 1 then Time = Time.."0"..hour; else Time = Time..hour; end;
   local minute = tostring(DateTime.min);
   if #minute == 1 then Time = Time.."0"..minute; else Time = Time..minute; end;
   local sec = tostring(DateTime.sec);
   if #sec == 1 then Time = Time.."0"..sec; else Time = Time..sec; end;
   Time = tonumber(Time);
   -- Если ночная сделка, смещает дату на 1 день вперед
   if Time < 90000 then
      local seconds = os.time(DateTime);
      seconds = seconds + 24*60*60;
      DateTime = os.date("*t",seconds);
      Date = tonumber(DateTime.year);
      month = tostring(DateTime.month);
      if #month == 1 then Date = Date.."0"..month; else Date = Date..month; end;
      day = tostring(DateTime.day);
      if #day == 1 then Date = Date.."0"..day; else Date = Date..day; end;
      Date = tonumber(Date);
   end;
   local Operation = "";
   if CheckBit(trade.flags, 2) == 1 then Operation = "S"; else Operation = "B"; end;
 
   -- Добавляет сделку в массив
   local Trade = {};
   Trade.Account = trade.account;
   Trade.Sec_code = trade.sec_code;
   Trade.Num = trade.trade_num;
   Trade.Date = Date;
   Trade.Time = Time;
   Trade.Operation = Operation;
   Trade.Qty = tonumber(trade.qty);
   Trade.Price = tonumber(trade.price);
   Trade.Hint = "Счет: "..Trade.Account.."_Номер: "..trade.trade_num.."_Дата: ";
   if #day == 1 then Trade.Hint = Trade.Hint.."0"..day.."/"; else Trade.Hint = Trade.Hint..day.."/"; end;
   if #month == 1 then Trade.Hint = Trade.Hint.."0"..month.."/"..DateTime.year; else Trade.Hint = Trade.Hint..month.."/"..DateTime.year; end;
   if #hour == 1 then Trade.Hint = Trade.Hint.."_Время: 0"..hour..":"; else Trade.Hint = Trade.Hint.."_Время: "..hour..":"; end;
   if #minute == 1 then Trade.Hint = Trade.Hint.."0"..minute..":"; else Trade.Hint = Trade.Hint..minute..":"; end;
   if #sec == 1 then Trade.Hint = Trade.Hint.."0"..sec; else Trade.Hint = Trade.Hint..sec; end;
   Trade.Hint = Trade.Hint.."_Количество: "..trade.qty;
   Trade.Hint = Trade.Hint.."_Цена: "..trade.price;
 
   -- Получает путь к файлу инструмента в папке торгового счета
   local PathAccountSec = DataFolder..Trade.Account..'\\'..Trade.Sec_code..'.csv';
   local FileIndex = Trade.Account..'_'..Trade.Sec_code;
   -- Если файл еще не открыт, или не существует
   if TradesFiles[FileIndex] == nil then
      -- Пытается открыть файл текущего инструмента в режиме "дописывания"
      TradesFiles[FileIndex] = io.open(PathAccountSec,"a+");
      -- Если файл не существует, то сделка не записана
      if TradesFiles[FileIndex] == nil then 
         -- Создает файл в режиме "записи"
         TradesFiles[FileIndex] = io.open(PathAccountSec,"w");
         -- Закрывает файл
         TradesFiles[FileIndex]:close();
         -- Открывает уже существующий файл в режиме "дописывания"
         TradesFiles[FileIndex] = io.open(PathAccountSec,"a+");
      end;
   end;
   -- Встает в начало файла
   TradesFiles[FileIndex]:seek("set",0);
   -- Если файл пустой
   if TradesFiles[FileIndex]:read() == nil then
      -- Добавляет строку заголовков
      TradesFiles[FileIndex]:write("Счет;Код бумаги;Номер сделки;Дата сделки;Время сделки;Операция;Количество;Цена;Текст подсказки", "\n");
   end;
   -- Встает в конец файла
   TradesFiles[FileIndex]:seek("end",0);
   -- Записывает сделку в файл
   TradesFiles[FileIndex]:write(Trade.Account..";"..Trade.Sec_code..";"..Trade.Num..";"..Trade.Date..";"..Trade.Time..";"..Trade.Operation..";"..Trade.Qty..";"..Trade.Price..";"..Trade.Hint, "\n");TradesFiles[FileIndex]:flush();
end;
 
function OnTrade(trade)
   -- Если данная сделка еще не записана в файл истории
   if not CheckTradeInFile(trade) then        
      -- Добавляет сделку в файл истории
      AddTradeInFile(trade);
   end;
end;
 
function OnStop()
   -- Закрывает все файлы
   for key,Handle in pairs(TradesFiles) do
      if Handle ~= nil then Handle:close(); end;
   end;
   Run = false;
end;
 
-- Функция возвращает значение бита (число 0, или 1) под номером bit (начинаются с 0) в числе flags, если такого бита нет, возвращает nil
function CheckBit(flags, bit)
   -- Проверяет, что переданные аргументы являются числами
   if type(flags) ~= "number" then error("Ошибка!!! Checkbit: 1-й аргумент не число!"); end;
   if type(bit) ~= "number" then error("Ошибка!!! Checkbit: 2-й аргумент не число!"); end;
   local RevBitsStr  = ""; -- Перевернутое (задом наперед) строковое представление двоичного представления переданного десятичного числа (flags)
   local Fmod = 0; -- Остаток от деления 
   local Go = true; -- Флаг работы цикла
   while Go do
      Fmod = math.fmod(flags, 2); -- Остаток от деления
      flags = math.floor(flags/2); -- Оставляет для следующей итерации цикла только целую часть от деления           
      RevBitsStr = RevBitsStr ..tostring(Fmod); -- Добавляет справа остаток от деления
      if flags == 0 then Go = false; end; -- Если был последний бит, завершает цикл
   end;
   -- Возвращает значение бита
   local Result = RevBitsStr :sub(bit+1,bit+1);
   if Result == "0" then return 0;     
   elseif Result == "1" then return 1;
   else return nil;
   end;
end;

2 (2021-12-14 20:36:59 отредактировано kalikazandr)

Re: СКРИПТ "ХРАНИТЕЛЬ СДЕЛОК"

RRR пишет:

Как-то нарыл один почти простой скрипт, который выводит совершённые сделки в экселевский файл.

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

встраиваете код ниже в любого вашего робота, по завершению работы которого, нужно вызвать save_trade().
либо используйте как самостоятельного бота - по окончании торговли запустить и остановить.
сделки будут сохранены в папку со скриптом.

--save_trade.lua
local exitflag
local path = getScriptPath()

--направление сделки
local function trades_bs(flags)
    if bit.band(flags, 0x4) ~= 0 then
        return "Продажа"
    else
        return "Купля"
    end
end
local function save_trade()
    local n = getNumberOf("trades")
    if n < 1 then return end
    
    local t = getItem("trades", n-1)
    local curr_date = os.date("%Y.%m.%d", os.time(t.datetime))
    local f = io.open(path.."\\trades_"..curr_date..".csv",'w')
    f:write("Номер;ТС;Дата;Время;Код класса;Инструмент;Операция;Цена;Кол-во;Объем\n")
    local res, px = {}
    for i=0, n-1 do
        t = getItem("trades", i)
        px = os.time(t.datetime)
        if os.date("%Y.%m.%d", px) == curr_date then
            res[#res+1] = t.trade_num..";"..
                t.account..";"..
                os.date("%Y.%m.%d;%H:%M:%S",px)..";"..
                t.class_code..";"..
                t.sec_code..";"..
                trades_bs(t.flags)..";"..
                string.gsub(tostring(t.price), "%.", ",")..";"..
                math.floor(t.qty)..";"..
                string.gsub(tostring(t.value), "%.", ",")
        end
    end
    f:write(table.concat(res,"\n")); f:flush(); f:close()
end
function main()
    while not exitflag do
        --some code
        sleep(1)
    end
    save_trade()
end
function OnStop()
    exitflag = true
    return 300
end

з.ы. луа не любит кириллицу, папки и названия файлов нужно писать латиницей.

3

Re: СКРИПТ "ХРАНИТЕЛЬ СДЕЛОК"

kalikazandr пишет:
RRR пишет:

код в помойку, самое правильное решение.

Согласен, так и придется сделать, просто праздное любопытство...
Ваш скрипт на много красивее и лаконичней, мне он больше нравится.
Такой вопрос, getNumberOf() ведь не работает с значениями в таблице "Клиентский портфель",по крайне мере ничего  подобного я не смог найти. Хотел добавить столбцы ВходящиеСредства, ТекущиеСредства, что бы учитывать изменения средств после каждой сделки, это возможно или я не там где смотрел...?

4 (2021-12-16 01:05:41 отредактировано kalikazandr)

Re: СКРИПТ "ХРАНИТЕЛЬ СДЕЛОК"

RRR пишет:

Хотел добавить столбцы ВходящиеСредства, ТекущиеСредства, что бы учитывать изменения средств после каждой сделки, это возможно или я не там где смотрел...?

getPortfolioInfo (STRING firm_id, STRING client_code)
вместо ТекущиеСредства, лучше использовать НаПокупНеМаржин, т.к. этот параметр отображает реальные ваши средства без учета позиций, открытых по маржируемым инструментам.
например, купив акции сбера, вы заплатите не единицу денег, а 1 * d_long, где d_long - понижающий коэфф.
в ВТБ24 (у меня) он 0.22, что означает, что вместо 100тыс.руб с меня возьмут всего 22тыс.руб. и ТекущиеСредства будут завышены, относительно реальной позиции.
коэфф. можно посмотреть в таблице купить/продать (щелкнуть 2 раза левой кнопкой мышки по таблице "Клиентский портфель").