Файл CSV-формата это обычный текстовый файл, с которым Excel и аналогичные программы могут работать как с таблицей. Каждая строка таблицы в этом файле записывается как новая строка со знаком переноса в конце, а значения полей разделены между собой каким-то символом, чаще ";". В самой первой строке такого файла можно (не обязательно) указать названия столбцов, так же через ";".
Ниже приведен пример создания такого файла и записи в него данных о совершенных сделках средствами QLua(Lua):
Код скрипта QLua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | Run = true; -- Флаг поддержания работы скрипта TradeNums = {}; -- Массив для хранения номеров записанных в файл сделок (для предотвращения дублирования) -- Вызывается терминалом QUIK в момент запуска скрипта function OnInit() -- Создает, или открывает для чтения/добавления файл CSV в той же папке, где находится данный скрипт CSV = io.open(getScriptPath().."/MyTrades.csv", "a+"); -- Встает в конец файла, получает номер позиции local Position = CSV:seek("end",0); -- Если файл еще пустой if Position == 0 then -- Создает строку с заголовками столбцов local Header = "Дата и время;Код класса;Код бумаги;Номер сделки;Номер заявки;Операция;Цена;Количество\n" -- Добавляет строку заголовков в файл CSV:write(Header); -- Сохраняет изменения в файле CSV:flush(); end; end; -- Основная функция скрипта (пока работает она, работает скрипт) function main() -- Цикл, поддерживающий работу скрипта while Run do sleep(100); end; end; -- Вызывается терминалом QUIK в момент остановки скрипта function OnStop() -- Выключает флаг, чтобы остановить цикл while внутри main Run = false; -- Закрывает открытый CSV-файл CSV:close(); end; -- Функция вызывается терминалом QUIK при совершении Вами новой сделки -- (обычно вызывается по 2 раза для каждой сделки) function OnTrade(trade) -- Перебирает массив с номерами записанных сделок (в обратном порядке) for i=#TradeNums,1,-1 do -- Если данная сделка уже была записана, выходит из функции if TradeNums[i] == trade.trade_num then return; end; end; -- Если мы здесь, значит сделка не была найдена в числе уже записанных -- Добавляет в массив номер новой сделки TradeNums[#TradeNums + 1] = trade.trade_num; -- Вычисляет операцию сделки local Operation = ""; if CheckBit(trade.flags, 2) == 1 then Operation = "Продажа"; else Operation = "Покупка"; end; -- Создает строку сделки для записи в файл ("Дата и время;Код класса;Код бумаги;Номер сделки;Номер заявки;Операция;Цена;Количество\n") local TradeLine = os.date("%c", os.time(trade.datetime))..";".. trade.class_code..";".. trade.sec_code..";".. trade.trade_num..";".. trade.order_num..";".. Operation..";".. trade.price..";".. trade.qty.."\n"; -- Записывает строку в файл CSV:write(TradeLine); -- Сохраняет изменения в файле CSV:flush(); 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; |
Скрипт создает файл следующего вида:
Если у Вас появились какие-то вопросы, задайте их в комментариях под статьей !!!
Здравствуйте Дмитрий!
Решил подкорректировать скрипт.
Цель: скрипт, вносит в таблицу сделки только с определенным маркером в ['CLIENT_CODE']
1 этап.
Решил посмотреть результат в таблице (CSV) чтоб в добавленный столбец CLIENT_CODE вносились данные из таблицы сделок столбец комментарии.
В заполнение структуры для отправки транзакции добавил параметр T['CLIENT_CODE'] = 'NAME1' (спс за подсказку)
Результат: Заявки отправляются, и в таблице сделок в столбе комментарии напротив заявки прописывается "1277//NAME1"
В скрипте "ИЗ QLUA (LUA) В EXCEL (CSV)"
в "local Header" добавил наименование столбца CLIENT_CODE
и в блок "Создает строку сделки для записи в файл TradeLine =..." добавил CLIENT_CODE.
Результат: робот создает таблицу и вписывает в столбик CLIENT_CODE, но только "1277".
Подскажите, как добиться чтоб в файле CSV в столбик CLIENT_CODE вписывался полный комментарий 'CLIENT_CODE' , в данном случае "1277//NAME1"?
спасибо
Здравствуйте. В таблице сделок поле Комментарий называется trade.brokerref, его и пишите в CSV. С Наступающим Вас! 🙂
И Вас с НГ, Здоровья Вам!!!
Здравствуйте!
Скажите пожалуйста, возможно ли на QLua дать команду Квику выгрузить таблицы по DDE в эксель?
(параметры выгрузку уже заданы вручную в диалоговом окне Квика)
Таблицы следующие:
Обезличенные сделки,
Клиентский портфель,
Позиции по клиентским счетам,
Таблица сделок,
Таблица заявок,
Таблица стоп-заявок
Здравствуйте, такой возможности нет, к сожалению.
Дмитрий, благодарю за ответ!
Может быть существует другой способ программно дать команду квику выполнить ряд действий?
Например, каждый день приходится руками исправлять список бумаг для выгрузки в Эксель таблицы обезличенных сделок (добавлять новые опционы и т.д.)
Можно через WinAPI к элементам квика (окна, кнопки и т.п.) обращаться, но там нужно долго разбираться. Проще весь вывод в Excel сделать из QLua без DDE, есть для этого библиотека luacom.dll.
Спасибо.
Всегда пожалуйста 🙂
Добрый день! Подскажите, пожалуйста, как при записи в файл числа с дробной частью заменить "." точку на "," запятую? Вариант: заменить в экселе не подходит, т.к. часть значений с точкой эксель в даты пытается превратить. Нужно как-то через код, либо в Квике где можно поменять "." точку на ","? Спасибо
Добрый день!
https://quikluacsharp.ru/qlua-osnovy/funktsii-raboty-so-strokami-v-qlua-lua/
Дмитрий! Помогите разобраться:
Не могу воткнуть последнюю сделку в CSV, ибо происходит она по закрытию скрипта на закрытие позиций в функции OnStop, в которой вызывается функция close_positions, но оттуда она уже не идет в функцию OnTrade(сужу по логам). Можно в принципе не выдумывать велосипед пытаясь ее послать куда надо, вопрос будет таков:
Можно ли записать последнюю сделку по-другому в тот же файл, допустим сразу после исполнения сделки?
Я бы и сам смог разобраться, если бы понял откуда взять переменную trade.trade_num откуда это берется?
я понял откуда, но почему это работает только в функции Ontrade?
Так и это я тоже понял)) но получается что робот вообще не знает о том что сделка совершилась, мда...
просто у Вас после выполнения функции OnStop робот отключается и OnTrade, как функция обратного вызова, срабатывающая по событию сделки, не успевает сработать.
Придумал как по другому сделать, но жаль, что это не работает обычным путем)
Я верю в Вас 🙂
в итоге запилил отдельного робота, но тот вообще ничего не пишет, хотя лог пишет как надо.
Работает несколько роботов, но теперь чтобы не было проблем с последней сделкой я решил сделать одного "отчетного* отдельно, но тот ничего не пишет, может я чего то недопонимаю, подскажите, пожалуйста, куда копать.
Что и для чего Вы внутри функции OnTrade пытаетесь проверить вот этой строкой ?:
Это чтобы он писал от разных роботов в разные массивы и из разных массивов писал в один файл, это нужно чтобы в файле в который идет запись указывался какой робот сделал эту сделку, я их пометил p_numberROBOT1 до 4, так же массивы.
Соответственно каждый массив проверяется на свои условия, в данном случае ((id=="10" or id=="11")
and p_seccode=="BRJ8"
and p_account=="SPBFUTJRaGY"
Вот, например, у Вас переменной p_seccode где и когда значение присваивается?
Вот так понимаю:
А как у Вас не понимаю
точно... я врубился... очень глупая ошибка)) спасибо! не знаю что бы без Вас делал)
Я брал ее из шапки, когда как должен был подгружать.
Всегда пожалуйста, просто будьте внимательнее, программирование - точная наука 🙂
Здравствуйте!
Работаю с двумя роботами оба должны писать в разные CSVешки, указал путь в функции OnInit до разных файлов в одной папке.
В итоге пишет сделки в обе CSV и так делает каждый робот!
Использую Ваш скрипт из примера выше для перегонки в CSV .
Помогите понять отчего такое происходит?
Пишут в оба файла, когда работают одновременно
Здравствуйте, покажите функции OnInit обоих скриптов, ну и, желательно, те места, где они пишут в файл, ошибка однозначно какая-то банальная, просто не внимательно все проверили, потому что чудес в программировании не бывает, компьютер всегда делает только то, что Вы ему указали делать.
Робот 1
Робот 2
Я что-то не вижу где Вы проверяете код бумаги, следовательно у Вас любая новая сделка записывается в оба файла.
ААААА!! точно!Я понял))) спасибо!
Всегда пожалуйста 🙂
и вот снова беда!
Сделал целых 2!! проверки одна на инструмент другая по ИД.
По инструменту работает проверка, но у меня теперь уже 4 робота, 2 по SI 2 по BR, от брент роботов это помогло, но 2 SI робота теперь пишут все в один файл, хотя указаны для них в функции OnInit разные файлы. В итоге в двух CSV по SI сделки с них обоих.
Возможно я как то не правильно проверку по id делаю, подскажите, пожалуйста, в чем я не прав тут.
что такое id, что такое p_seccode, где Вы их взяли?
сек-код правильно считается так бы и брент писал, задан он в шапке робота как и в вашем МА роботе для примера, впрочем так все делают вроде)
насчет id:
p_TRANS_ID p_и TRANS_ID_STOP также в шапке указаны под значениями, чтобы робот не путал свои транзакции с другими.
А у Вас trans_id у всех заявок один и тот же задается при отправке транзакции?
нет, во всех роботах разные поставил конечно же в одном 2 и 3, в другом 4 и 5 и т. д.
впрочем я пока что нашел альтернативу: указывать в проверке счет ибо он разный - помогло. Хотя почему не хочет по ИД различать я не могу в толк взять, все равно интересно, так что если есть мысли буду благодарен за помощь!
Посмотрите в таблице сделок какой там реально id отображается, совпадает ли он с Вашим предполагаемым
Да все ID расписаны как надо в таблице сделок.
Открою Вам страшный секрет поиска ошибок 🙂
Если message выведет то же число, с которым Вы сравниваете, то значит Вы сравниваете число со строкой, или наоборот
так, разобрался! Спасибо!
Еще вопросец, можно ли как то сделать, чтобы после совершения сделки по закрытию позы робот не совершал никаких операций в течении одной свечи?
Пожалуйста всегда. Запоминайте индекс текущей свечи при закрытии позы и ждите следующего индекса.
А можете дать пример, если Вам не сложно?
У меня нет такого готового примера, и если я Вам дам такой пример, Вы хуже научитесь, разберитесь в этом вопросе самостоятельно, эффект будет значительно выше, особенно Вы это заметите когда в следующий раз столкнетесь с подобной задачей, все, что Вам для этого нужно, это внимательно изучить эту статью, а затем потестировать в отдельном скрипте как это все работает, не пытайтесь сразу пихать в робота то, в чем Вы еще не разобрались.
https://quikluacsharp.ru/quik-qlua/poluchenie-v-qlua-lua-dannyh-iz-grafikov-i-indikatorov/
получение данных из графиков я умею производить, но вот как описать модель - ждать следующей свечи не понимаю, как это описать можно)
Попробую разобраться! Спасибо!
Вроде бы ведь все просто:
А как еще это можно сделать? Логика-то ведь простая 🙂
С утра или после дисконекта, индексы запомненные, ни о чём не скажут.
А вот время свечи всегда уникально:
local bar_posix = 0
local CloseIndex = 0
local size = ds:Size()
-- Поза закрылась
bar_posix = os.time(ds:T(size))
Совсем от индекса не нужно отказываться, просто после дисконекта/включения ищем индекс бара с:
if bar_posix == os.time(ds:T(size)) then
CloseIndex = ds:Size()
...
Привет!
1. Самая ужасная ошибка - это использование функции CheckBit в функциях обратного вызова т.к. в ней есть цикл и на время его выполнения терминал курит нервно в сторонке и копит данные. Ну и иже с ним вот этот цикл: for i=#TradeNumsSI,1,-1 do - зачем?
2. local Position = CSV:seek("end",0) зачем? У вас автоматом при открытии файла 'a+' добавит в конец файла строку.
"--Перебирает массив с номерами записанных сделок (в обратном порядке) " - это тоже решение не "фонтан":
TradeNumsSI = {}
OrderNumsSI = {}
function OnOrder(order)
local order_num = order.order_num
OrderNumsSI[order_num] = order.qty -- запомнили кол-во контрактов в заявке
-- если заявка снята, то нужно поправить: OrderNumsSI[order_num] = order.qty - order.balance
end
function OnTrade(trade)
local trade_num = trade.trade_num
-- Всегда! сначала проверяем только то, что точно не даст возможности дальнейшего выполнения кода в блоке
if not trade_num or TradeNumsSI[trade_num] then return end
TradeNumsSI[trade_num] = true -- больше не будем считать эту сделку
local order_num = trade.order_num
OrderNumsSI[order_num] = OrderNumsSI[order_num] - trade.qty
if OrderNumsSI[order_num] == 0 then -- вот и всё - сделок по этой заявке больше не будет совсем, можно и в файл сохранить
... some code
end
end
ЗЫ: и вообще, Дмитрий, функцию CheckBit ее собратьев с циклами ожидающими, нужно удалить с сайта, дабы не вводить в заблуждение народ )))
Например, на эти:
function OrderStatus(flags)
if bit.band(flags, 0x1) ~= 0 then return "A"
else
if bit.band(flags, 0x2) ~= 0 then return "K"
else return "F"
end
end
end
function Operation(flags) -- применима к "orders", "stop_orders", "trades", "all_trades"
if bit.band(flags, 0x4) ~= 0 then return "S" -- лучше использовать не букву"S", а цифру: -1
else return "B" -- лучше использовать не "B", а цифру: 1
end
end
local direction = Operation(flags)
local posa = qty * direction
Как-то так, примерно.
Сообщение выше для Vashigor777.
Дмитрий, постоянно плююсь при добавлении комментария, ужос просто
Да, согласен, но раньше не было bit.test, а, вообще, по уму, конечно в массив сделки писать, а его уже в main обрабатывать.
Да в квике пофик по идее, пакеты событий не чаще 10 раз в секунду идут, если пинг нормальный.
Актуально в майн сносить только события обезличенных сделок. Факториал же не считаешь в OnTrade ))
Здравствуйте. как точку заменить на запятую, в вашем примере что б цена в таблице была не 103.39, а 103,39?
Здравствуйте!
спасибо.
можно расшифровать что тут что делает?)
Пожалуйста, про gsub можете здесь посмотреть: https://quikluacsharp.ru/qlua-osnovy/funktsii-raboty-so-strokami-v-qlua-lua/
Это шаблон поиска '[\.]+', о том как они строятся можете почитать погуглив "регулярные выражения"
Можно проще сделать:
tostring преобразует число в строку, gsub производит поиск в строке и все совпадающие с шаблоном поиска элементы заменяет шаблоном замены, в данном случае точки на запятые. Так как символ точка в регулярных выражениях означает "любой символ", то для поиска именно точки нужно его экранировать, для этого в Lua используется символ "%"
а как сделать что б ячейки в таблице по ширине сразу были по размеру записи?
Это нужно в Excel после открытия файла уже настраивать автоматическую ширину столбца, может быть можно в Excel сделать предустановленные настройки для открываемых файлов CSV, но я не знаю как это сделать. Вообще, CSV файл это обычный текстовый файл, в котором хранятся только значения ячеек и не хранится никакой информации для Excel о том, как его правильно отображать.
Здравствуйте. А есть возможность реализовать вывод инфо по закрытым сделкам? Т.е. дата/время, цена, объем открытия, и тут же в строке дата/время и цена закрытия? Что-то вроде отчета брокера.
Если рассмотреть лот размером 1, то можно просто запоминать данные открытия и при закрытии экспортировать их вместе с текущими.
Когда же лот равен 2 и выше, то он часто исполняется частями, а если еще и сделка была открыта позавчера, а закрыта сегодня, то тогда такой метод не подойдет.
Здравствуйте, можете просто сформировать строку из нужных Вам данных и таким же образом записать ее в файл.Данные можно брать из соответствующих таблиц квика, а если Вам нужно отслеживать и вчерашние сделки, то тут нужен комплексный подход, не только писать в файл, но и читать из него.
Да, я это понимаю. Тут вопрос не сколько в программировании, сколько по торговле. Я не могу сообразить, как из таблиц понять и формализовать, что пришла сделка, закрывающая предыдущую. Конечно, если они друг за другом идут (открылся/закрылся), то понятно. У вас опыта больше, как я думаю, вот и интересуюсь.
Пробую аккумулировать совершенные сделки в массиве и, как есть у вас один пример, перебирать и взаимоубирать встречные, но пока нет уверенности, что будет правильно, пока проверяю.
Как вы думаете, как правильно перебрать много сделок и из них собрать строки для выгрузки по закрытым?
Скорее всего нужно в функции OnTrade мониторить новые сделки, когда появилась новая сделка, смотрите таблицу ограничений по клиентским счетам(фьючерсы), в ней есть поле текущая чистая позиция (totalnet), по изменению этого значения будет понятно что за сделка прошла, только там нужно посмотреть, я сейчас не помню, возможно в момент срабатывания функции OnTrade значение totalnet еще не поменяется, тогда, наверное, не нужно обрабатывать данную сделку, а подождать следующего вызова OnTrade, тогда уже, наверное, totalnet поменяется, OnTrade по несколько раз вызывается для одной и той же сделки. В общем, нужно опытным путем проверять.
Для справки:
функции обратного вызова https://quikluacsharp.ru/qlua-osnovy/funktsii-obratnogo-vyzova-vstroennye-v-qlua/
получение данных из таблиц Quik https://quikluacsharp.ru/quik-qlua/poluchenie-dannyh-iz-tablits-quik-v-qlua-lua/
Да, так можно. Только если позиция набиралась частями (разные объемы, цены) и закрывалась через несколько дней, то так тоже не отследить.
Конечно, однозначно нужно хранить все совершенные сделки и при уменьшении чистых позиций производить анализ. Только не могу сообразить, чем общим у открытой и закрытой сделки оперировать. При отправке транзакции добавляю комментарий, что упрощает работу, но т.к. сделка наливается и исполняется частями, то не ясно, какую с какой сделкой связывать.
В любом случае, спасибо за совет, буду тогда думать дальше.
У каждой частичной сделки есть общий номер заявки.
Точно, мой косяк, понесло в сторону. Спасибо вам!
А не, не понесло 🙂 Вы имеете в виду, когда наливается заявка, то номер заявки один на всех, да, это так, но не это интересно. У сделки есть продажа со своим номером заявки и есть закрывающая ее покупка, со своим номером заявки. И они разные.
Если сделка продажа в 10 лотов, а потом выставляется ТП для покупки 10 лотов, то ордер ТП имеет уже совсем другой, не связанный с первой сделкой продажей, номер заявки. Как потом в таблицах понять, что это закрывающая сделка? Ну и нужно помнить, что наливается частями, что немного усложняет ситуацию.
Выходит нужно смотреть по цене и разнонаправленности. Ну или вести массив с ключом ценой открываемой сделки и собирать туда все данные. Ладно, буду думать дальше.
Здравствуйте, Дмитрий!
Как понимаю, в Qlua не поддерживается "getFileLen" или "getFileLength", кок в таком случае считать и использовать значение из конкретной ячейки внешнего файла? Или ячейки заданного столбца в последней значимой строке?
Здравствуйте, в документации есть вот такая функция:
можете ее использовать для получения размера файла.
Чтобы получать значения ячеек, нужно учитывать то, что CSV это обычный текстовый файл, в котором есть строки, а значения в строке разделяются символом ";", обычно.
Вот пример работы с ним:
Спасибо! То есть получить индекс (порядковый номер) последней строки можно только через цикл? И получается можно использовать обычный *.txt с разделителем, типа ";" ?
Всегда пожалуйста!
Возможно есть какой-то другой способ узнать количество строк, но я его не знаю, к сожалению. Можете использовать любое расширение файла, Lua, в данном случае, все равно будет обращаться с ним, как с .txt
Дмитрий, в приведенном выше скрипте, цикл for str in line:gmatch("[^;^\n]+") do продолжает суммировать кол-во столбцов не зависимо от значения line, то есть если в файле две строки и пять столбцов, то увидим message(RowNum..','..CellNum..': '..tostring(str)) "2,10 какое-то значение"
А значения ячеек правильно выводит?
Значение выводит правильное. Если убрать "+" , то выводит познаково и считает кол-во знаков, типа 2,236: t 2,237: e 2,238: s 2,239: s 2,240: t
Что у Вас в файле записано?
Я сейчас создал на диске С текстовый файл следующего содержания:
1;2;3;4;5
6;7;8;9;10
переименовал его в MyCSVFile.csv,
запустил скрипт, и он вывел:
1,1: 1
1,2: 2
1,3: 3
1,4: 4
1,5: 5
2,1: 6
2,2: 7
2,3: 8
2,4: 9
2,5: 10
Как и ожидалось!
В файле
1;2;3
9;8;7
вывел:
1,1: 1
1,2: 2
1,3: 3
2,4: 9
2,5: 8
2,6: 7
Чудеса какие-то, пришлите на почту мне Ваш скрипт и этот файл, если Вы 10-ю строку не удалили
То, что в примере написано в 10-й строке в Вашем скрипте отсутствует, почему-то, будьте внимательны
Разве local CellNum = 0 -- номер ячейки не присваивает переменной значение ноль?
Если Вы так задаете вопрос, то лучше Вы мне ответьте на вопрос: для чего в данном скрипте нужна эта переменная и какова логика ее работы?
Понял, при следующем проходе цикла обнуляет значение столбцов. Спасибо!
Да, всегда пожалуйста
Спасибо, очень полезная статейка.
Простенько и со вкусом можно записывать свои сделки, заявки и все что душа пожелает, даже не нажимая кнопок.))
Спасибо! В CSV нельзя создавать еще и листы? Чтобы какие-то данные были в одном файле, но на разных листах.
К сожалению нет, только если несколько файлов делать.
Доброй ночи. А обратно из Excel в QLua возможно данные передавать?
Через библиотеку LuaCom ? Если через нее может есть ссылка на безопасное скачивание? Не рискую качать неизвестно где))
Здравствуйте, если нужно работать с полноценными таблицами Excel, то нужна LuaCom, а если с CSV работать, то это обычный текстовый формат, который можно читать стандартными средствами lua.
Вот ссылка на скачивание с оф.сайта: http://files.luaforge.net/releases/luacom/luacom/1.4/luacom-1.4-luabinaries.zip
Вот документация на английском: http://files.luaforge.net/releases/luacom/luacom/1.4/luacom-1.4-doc.pdf
Спасибо буду разбираться.
Цитата - "если с CSV работать, то это обычный текстовый формат, который можно читать стандартными средствами lua.", у Вас были статьи про то как это делать?
Всегда пожалуйста! Не было таких статей, но там все просто: