В статье "Обмен данными между DLL (C/C++) и приложением C#" показан простой пример отправки сообщений из DLL в приложение C#, а QUIK(Lua) просто запускает и останавливает эту отправку. Для отправки команд из приложения C# в QUIK лучше использовать примерно следующую конструкцию:
QLua-функция main()
-- с периодичность в 1 миллисекунду проверяет не пришла ли команда из C# function main() while IsRun do -- Если функция вернула не пустую строку local CommandStr = tostring(QluaCSharpConnector.GetCommand()); if CommandStr ~= "" then -- здесь будет Ваш код обработки команды end; sleep(1); end; end; |
DLL-функция (C/C++) GetCommand()
(Если Вы не знаете как создавать библиотеки DLL, которые можно использовать в скриптах QLua(Lua), ознакомьтесь, пожалуйста, с данной статьей).
// Имя для выделенной памяти TCHAR Name[] = TEXT("QUIKCommand"); // Создаст, или подключится к уже созданной памяти с таким именем HANDLE hFileMapQUIKCommand= CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 256, Name); static int forLua_GetCommand(lua_State *L) { //Если указатель на память получен if (hFileMapQUIKCommand) { //Получает доступ к байтам памяти PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapQUIKCommand, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 256)); //Если доступ к байтам памяти получен if (pb != NULL) { //Если память чиста if (pb[0] == 0) { //Записывает в Lua-стек пустую строку lua_pushstring(L, ""); } else //Если в памяти есть команда { //Записывает в Lua-стек полученную команду lua_pushstring(L, (char*)(pb)); //Стирает запись, чтобы повторно не выполнить команду for (int i = 0; i < 256; i++)pb[i] = '\0'; } //Закрывает представление UnmapViewOfFile(pb); } else lua_pushstring(L, "");//Если доступ к байтам памяти не был получен, записывает в Lua-стек пустую строку } else //Указатель на память не был получен { //Записывает в Lua-стек пустую строку lua_pushstring(L, ""); } //Функция возвращает записанное значение из Lua-стека (пустую строку, или полученную команду) return(1); } |
Код C#
...
// Создаст, или подключится к уже созданной памяти с таким именем public MemoryMappedFile MemoryQUIKCommand; // Создает поток для чтения StreamReader SR_QUIKCommand; // Создает поток для записи StreamWriter SW_QUIKCommand; |
...
//выделяет именованную память размером 256 байт для отправки КОМАНД в QUIK, создает потоки чтения/записи MemoryQUIKCommand = MemoryMappedFile.CreateOrOpen("QUIKCommand", 256, MemoryMappedFileAccess.ReadWrite); SR_QUIKCommand = new StreamReader(MemoryQUIKCommand.CreateViewStream(), System.Text.Encoding.Default); SW_QUIKCommand = new StreamWriter(MemoryQUIKCommand.CreateViewStream(), System.Text.Encoding.Default); |
...
//Функция отправки команды в QUIK ( вызов: SetQUIKCommandData("Ваша команда"); ), или очистки памяти, при вызове без параметров ( вызов: SetQUIKCommandData(); ) private void SetQUIKCommandData(string Data = "") { //Если нужно отправить команду if (Data != "") { //Дополняет строку команды "нулевыми байтами" до нужной длины for (int i = Data.Length; i < 256; i++) Data += "\0"; } else //Если нужно очистить память { //Заполняет строку для записи "нулевыми байтами" for (int i = 0; i < 256; i++) Data += "\0"; } //Встает в начало SW_QUIKCommand.BaseStream.Seek(0, SeekOrigin.Begin); //Записывает строку SW_QUIKCommand.Write(Data); //Сохраняет изменения в памяти SW_QUIKCommand.Flush(); } |
Пробовал реализовать отправку команды из C# , но Quik выдаёт ошибку.
C:\Share\QUIK\testConnector.lua:9: attempt to call field 'GetCommand' (a nil value)
Ссылка на пример:
https://quikluacsharp.ru/qlua-c-cpp-csharp/kak-otpravit-komandu-v-quik-iz-prilozheniya-c/
Dll создал и поместил в корень терминала.
Содержимое файла:
Что я делаю не так?
Может в Квике нужно разрешать где-то приём внешних данных?
А вообще огромное спасибо за статьи, правда доработать пришлось обозначение сделок на графике, и работает на мой взгляд не совсем корректно, но так лучше чем просто Квик.
Здравствуйте! Выложите здесь код своей DLL, я завтра посмотрю.
Дмитрий, добрый вечер.
Подскажите пожалуйста. Не получается найти ошибку((
Выставлена заявка, и хочу ее снять. Посылаю команду TRANS_ID=5850;ACTION=KILL_ALL_FUTURES_ORDERS;CLASSCODE=SPBFUT;SECCODE=RIZ5;BASE_CONTRACT=RIZ5;ACCOUNT=SPBFUT00501;OPERATION=B;
в ответ QUIK пишет "Ошибка снятия заявок по условию. [FORTS][36] "Попытка операции по несуществующему базовому активу.".
В чем может быть причина.
С уважением, Александр.
Добрый вечер, Александр! У Вас, скорее всего, поле SEC_CODE лишнее, вот пример транзакции снятия всех заявок на рынке FORTS ("Руководство пользователя QUIK" -> "Раздел 6. Совместная работа с другими приложениями" -> "Импорт транзакций" -> "Формат .tri-файла с параметрами транзакций" -> "Примеры строк, которые могут содержаться в файле"):
TRANS_ID=50; ACCOUNT=SPBFUT00001; ACTION=KILL_ALL_FUTURES_ORDERS; OPERATION=B; CLASSCODE=SPBFUT; BASE_CONTRACT=RTKM;
Дмитрий, спасибо.
Я то же пользуюсь этим примером.
Наверно это из за противоречия
ACTION=KILL_ALL_FUTURES_ORDERS; -->>> SECCODE=RIZ5;BASE_CONTRACT=RIZ5;
исправлю и по пробую.
Спасибо за идею обмена данных через "кусок памяти"
Реализовал я свою задумку Amibroker(стратегия выставляет сигналы)-> привод QUIK0....QUIKN
В привод поступает сигнал от стратегий, привод управляет N Quik-ами в зависимости от инструмента и доступного капитала в режиме автомат/полу-автомат/ручное управление. Доделываю сервисные вещи. Самое сложное оказалась отладка кода LUA в Quikе очень капризные программы.
Дима, спасибо Вам за помощь. Без Вашего сайта и Ваших примеров все бы оказалась более сложно и громозко.
С уважением, Александр
Всегда пожалуйста, Александр!
"Я бы на каждый инструмент свой канал сделал" -я об этом и говорю в Amibrokere называют тикер.
Отличная статья)) хороший пример.
Теперь дело за малым начать и ....)) работать, работать ...
Шикарный сайт!!!! Спасибо Вам за такой огромный труд!!!!
Дай бог Вам здоровье))))))!!!
Понятно:) Просто не доводилось работать с Амиброкер) Еще раз спасибо Вам за добрые слова)
"сигналы из Amibroker передавать в C# по одному каналу, как я понял они для всех терминалов одинаковы" - предположим худший вариант 10 инструментов одновременно подали заявку и будут пытаться записать ее... выделить 10 каналов и каждому тикеру присвоить - это наверно лучшее решение. И это будет законченный модуль в который больше никогда не лазить!)))
"а из C# уже управлять терминалами посредством отдельной DLL по N каналам, "- да это будет самостоятельная программа которую можно будет до делать, переделать и в отладке гораздо легче.
Спасибо за советы. Точно понял в каком направлении двигаться.
Вопрос по Qlua можно "заставить" Quik передавать отдельно таблицы: сделок/заявок и прочее по каждому инструменту?
Позволю себе еще небольшое уточнение) Я бы на каждый инструмент свой канал сделал, а не на каждый тикер, потому то несколько инструментов могут одновременно отправить buy, например, а каждый инструмент в отдельности точно не отправит одновременно более 1-го тикера, хотя могу ошибаться.
По поводу отправки таблиц, можно воспользоваться функциями обратного вызова https://quikluacsharp.ru/qlua-osnovy/funktsii-obratnogo-vyzova-vstroennye-v-qlua/
Красивое решение )))
Но я думал весь привод вынести в C# а из текущей DLL (из Amibrokera) передавать
тикер, buy, sell, cover, short, myPosition - и как я понял сделать N каналов по тикерам.
Все остальное перенести в С#
И как Вы абсолютно правы привязать Quik к каждому каналу
Спасибо))) Может быть я Вас не совсем понял, но может быть лучше торговые сигналы из Amibroker передавать в C# по одному каналу, как я понял они для всех терминалов одинаковы и не появляются одновременно?, а из C# уже управлять N терминалами посредством отдельной DLL по N каналам, или я ошибаюсь?
Как сейчас работает.
1. Мой товарищ торгует стратегию вручную по 10 счетам и более, порядка 8 бумаг на клиента 4 фьюча + 4 акции. Десять клиентов деньги в управлении. Тяжко, но он уже много лет в очень хорошей прибыли.
2. Торгую роботом. Робот написан в Amibrokere, алгоритм написан на скриптовом языке, привод на С++. Привод в виде DLL встроил в Amibrokere. Связь с Quik происходит через AmiSharp "bot4sale.ru/blog-menu/amisharp-menu/amisharp-list/203-amisharp-what-is-this" .
Привод работает в ручном и в автоматическом режиме. Пишет историю сделок по каждому инструменту, логи и прочее. Количество инструментов пробовал 10 Все работает.
Задача:
Amibroker выдает управляющие сигналы по стратегии buy/sell и процент от максимального значения позиции (пример мах 10 контр купили 33% - 3 контракта). Также с закрытием в позиции было 100% закрыли 20% - 2 контракта.
Сигналы попадают на привод, в котором пересчитывается из % в кол-во в зависимости выделенных средств по каждому инструменту, в зависимости от конкретного терминала. И в терминал выставляются заявки.
Привод также должен уметь в ручном режиме:
Запустить/остановит процесс, купить/продать произвольное кол-во контрактов и закрыть сделку.
И это блок должен управлять N -кол QUIK.
"Вот для чего, например, у Вас переменное число терминалов будет участвовать в торговле ?" - подходят новые клиенты))
Благодарю за пояснения, Александр! Оптимальным, думаю, будет сделать следующее:
1. Между приложением C# и DLL сделать сервисный канал памяти, в котором будет передаваться количество используемых терминалов, DLL должна будет поддерживать работу данного числа каналов, т.е. если количество, передаваемое из C# изменилось, то DLL, так же, меняет количество используемых каналов динамически (отключается, или подключается к новому каналу). Создавать/удалять каналы нужно на стороне C#, именовать как-то типа "QUIK_1_Command", "QUIK_2_Command" и т.д. Таким образом и DLL и C# будут знать с какими каналами работает любой терминал со своим порядковым номером.
2. В C# и в DLL функции работы с памятью, выделенной для обменом данными с терминалами, должны быть универсальны, т.е., помимо нужной информации, в функцию нужно передавать порядковый номер терминала, с которым в данный момент идет работа. Таким образом функции смогут обращаться именно к нужной памяти.
3. В скрипте Qlua нужно будет менять только данные клиента и номер, который Вы присвоили терминалу, чтобы скрипт, обращаясь к DLL передавал одним из параметров функции этот номер и DLL знала какой именно терминал к ней обратился и работала с соответствующей памятью.
4. В C# нужно будет добавить кнопки типа "Добавить терминал", "Удалить терминал" и при отправке команды в ручном режиме выбирать, например, из выпадающего списка в какой именно терминал нужно отправить команду.
Как Вам такой алгоритм работы?
Спасибо за ответ.
1. Да если знаешь сколько терминалов (или заложиться на десяток)
2. Со стороны терминала жестко привязать к каналу?
3. Данные по каналу могут С++ => C# могут быть в форме оговоренной структуры?
Ну тут разные могут быть решения, все зависит от конкретных задач.
Есть вариант делать монопольный доступ к памяти, т.е. пока один терминал пишет в нее, другие ждут, но для трейдинга немаловажна скорость работы всей системы, а такой вариант будет тормозить процесс. Да и вообще, чем сложнее реализация, тем больше возможность ошибки, по-этому чем проще, тем лучше, я считаю) Вы бы обрисовали более подробно Вашу задачу, я бы с удовольствием подумал какое может быть оптимальное решение, а так сложно что-то конкретное посоветовать. Вот для чего, например, у Вас переменное число терминалов будет участвовать в торговле ?
Дмитрий, добрый день. Спасибо за сайт за интересные примеры.
Димитрий, а если передавать/принимать от нескольких квиков сигналы.
Как это все можно будет синхронизировать. На три квика, пришла заявка купить. Они стали одновременно отвечать, перезаписывать один файл обмена.
Или другой пример программа запросила таблицу сделок/заявок три квика сталь отвечать также в один файл обмена.
Как разрулить такую ситуацию?
Добрый день, Александр! Спасибо за добрые слова! По моему мнению разумнее для каждого терминала QUIK выделить свой канал памяти, это во всех смыслах удобнее и эффективнее. У меня для каждой задачи свой канал памяти, т.е. данные из таблицы всех сделок по одному каналу идут, команды по другому и т.д.
Добрый день! На указанный Вами адрес отправил проекты. Пробовал отладить. Сигнал с С# о том, что в память что -то записано проходит и dll пытается обработать, отправляя в Lua пустое сообщение. Если из dll в коде отправлять тестовое сообщение, то в Lua оно приходит. Куда пропадает сообщение с С# непонятно.
Добрый день, Виктор! На первый взгляд, сразу видна логическая ошибка в коде QLua!
Функция GetCommand(), получая команду из C# и отправив ее в QLua, сразу очищает память (строка 122), чтобы повторно ее не прочитать и не отправить в скрипт:
Т.е., когда Вы отправили команду из C#, то QLua (при первом обращении) в строке 32 получает эту команду, и функция GetCommand() благополучно очищает память. И когда Вы в строках 36 и 38 снова вызываете эту функцию, то в памяти "QUIKCommand" уже нет этой команды. Когда же Вы в DLL раскомментируете Вашу строку 124, то функция GetCommand() при каждом вызове возвращает в Qlua строку "ROSN,QJSIM" независимо от того, что в памяти "QUIKCommand" ничего нет.
Прошу прощения, это я, наверное, ввел Вас немного в заблуждение строкой в примере:
if tostring(QluaCSharpConnector.GetCommand()) ~= "" then,
естественно, нужно сначала записать значение, переданное функцией GetCommand() в переменную, чтобы потом работать с ним, с Вашей помощью я исправил этот недочет в статье.
Попробуйте исправить эту ошибку, а я сейчас еще более внимательно изучу Ваш код, может быть еще смогу дать Вам какие-то полезные советы.
Посмотрел, комментировать Qlua и DLL не буду, вижу, что Вы искали ошибку, а вот в C# Вы подключаетесь к памяти и инициализируете StreamReader, StreamWriter сначала в функции MainWindow(), а затем еще раз в функции SetQUIKCommandData(). Хотя возможно это тоже был результат поиска ошибки. В любом случае, достаточно указать этот код один раз в MainWindow(), а затем нужно при завершении приложения закрыть StreamReader, StreamWriter и уничтожить выделенную память. Сделать это лучше в функции Window_Closing(), добавив код:
SR_QUIKCommand.Close();
SW_QUIKCommand.Close();
SR_TerminalQuote.Close();
SW_TerminalQuote.Close();
MemoryQUIKCommand.Dispose();
MemoryTerminalQuote.Dispose();
Надеюсь помог! Жду новых вопросов!
Да действительно помогло. Просто надо будет иметь ввиду, то что информация при таком подходе может быть прочитана единожды. Спасибо большое!
Пример с получением стакана работает отлично, а вот попытка передать строку из С# в dll С++ по данному примеру никак не хочет. Это пример приведен для наглядности или он рабочий?
Здравствуйте, Виктор! Все примеры на сайте рабочие, единственное, что код данного примера приведен не в полном объеме, а только те функции, которые выполняют поставленную задачу, как для скрипта QLua, так и для DLL и для приложения C#. В каком именно месте у Вас команда не проходит? Еще на стороне C#, или в DLL? Вы пробовали делать какую-то отладку, чтобы отследить ошибку? Можете прислать полностью Ваш код, я посмотрю в чем причина. Можете либо здесь его выложить, либо прислать на почту reply@quikluacsharp.ru