При отправке из терминала QUIK таких часто изменяющихся данных, как "СТАКАН", необходимо использовать обратную связь от C# о получении данных. Так же, на стороне QLua необходим буфер (стек), в который будут заноситься и из которого, в последствии, будут отправляться новые данные, по мере получения их приложением C#. На практике этот процесс происходит очень быстро, так что данные не успевают задерживаться в стеке в ожидании своей очереди. Благодаря чему, приложение C# всегда своевременно получает актуальные изменения. А благодаря стеку, ни одно изменение не останется упущенным.
Примеры кода:
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | require("QluaCSharpConnector") IsStop = false; -- Флаг остановки скрипта Stack = {}; -- Массив для стека Stack.idx_for_add = 1; -- Индекс для добавления следующей, или первой записи Stack.idx_for_get = 1; -- Индекс для изъятия следующей, или первой записи Stack.count = 0; -- Количество находящихся в стеке записей Stack.max = 1000; -- Максимально возможное количество записей в стеке (при переполнении старые записи будут замещаться новыми) CLASS_CODE = "SPBFUT"; -- Класс бумаги SEC_CODE = "RIH5"; -- Код бумаги function main() local FirstQuote = true; local Quote = ""; -- ОСНОВНОЙ ЦИКЛ while not IsStop do -- если робот получил СТАКАН, или это первый СТАКАН if QluaCSharpConnector.CheckGotQuote() or FirstQuote then -- берет стакан из стека Quote = GetFromStack(); -- если стек не пустой if Quote ~= nil then if FirstQuote then FirstQuote = false; end; -- отправляет стакан роботу QluaCSharpConnector.SendQuote(Quote); end; end sleep(1); end; end -- Добавляет запись в стек function AddToStack(NewEntry) -- Добавляет запись в стек Stack[Stack.idx_for_add] = NewEntry; -- Корректирует счетчик находящихся в стеке записей if Stack.count < Stack.max then Stack.count = Stack.count + 1; end; -- Увеличивает индекс для добавления следующей записи Stack.idx_for_add = Stack.idx_for_add + 1; -- Если индекс больше максимально допустимого, то следующая запись будет добавляться в начало стека if Stack.idx_for_add > Stack.max then Stack.idx_for_add = 1; end; -- Если изъятие записей отстало от записи (новая запись переписала старую), то увеличивает индекс для изъятия следующей записи if Stack.idx_for_add - Stack.idx_for_get == 1 and Stack.count > 1 -- смещение внутри стека then Stack.idx_for_get = Stack.idx_for_get + 1; -- Добавил в конец, когда индекс для изъятия тоже был в конце и количество не равно 0 elseif Stack.idx_for_get - Stack.idx_for_add == Stack.max - 1 and Stack.count > 1 then Stack.idx_for_get = 1; end; end; -- Извлекает запись из стека function GetFromStack() local OldInxForGet = Stack.idx_for_get; if Stack.count == 0 then return nil; end; -- Уменьшает количество записей на 1 Stack.count = Stack.count - 1; -- Корректирует, если это была единственная запись if Stack.count == -1 then Stack.count = 0; Stack.idx_for_get = Stack.idx_for_add; -- Выравнивает индексы else -- Если еще есть записи -- Сдвигает индекс изъятия на 1 вправо Stack.idx_for_get = Stack.idx_for_get + 1; -- Корректирует, если достигнут конец if Stack.idx_for_get > Stack.max then Stack.idx_for_get = 1; end; end; return Stack[OldInxForGet]; end; --- Функция вызывается терминалом QUIK при получении изменения стакана котировок function OnQuote(class, sec ) if class == CLASS_CODE and sec == SEC_CODE then -- Получает стакан по нужному инструменту ql2 = getQuoteLevel2(class, sec); -- Представляет снимок СТАКАНА в виде СТРОКИ QuoteStr = ""; for i = tonumber(ql2.bid_count), 1, -1 do if ql2.bid[i].quantity ~= nil then QuoteStr = QuoteStr..tostring(tonumber(ql2.bid[i].quantity))..";"..tostring(tonumber(ql2.bid[i].price))..";"; else QuoteStr = QuoteStr.."0;"..tostring(tonumber(ql2.bid[i].price))..";"; end; end; for i = 1, tonumber(ql2.offer_count), 1 do if ql2.offer[i].quantity ~= nil then if i < tonumber(ql2.offer_count) then QuoteStr = QuoteStr..tostring(tonumber(ql2.offer[i].quantity))..";"..tostring(tonumber(ql2.offer[i].price))..";"; else QuoteStr = QuoteStr..tostring(tonumber(ql2.offer[i].quantity))..";"..tostring(tonumber(ql2.offer[i].price)); end; else if i < tonumber(ql2.offer_count) then QuoteStr = QuoteStr.."0;"..tostring(tonumber(ql2.offer[i].price))..";"; else QuoteStr = QuoteStr.."0;"..tostring(tonumber(ql2.offer[i].price)); end; end; end; -- Добавляет СТАКАН-строку в стек AddToStack(QuoteStr); end; end; --- Функция вызывается терминалом QUIK при завершении пользователем скрипта function OnStop(s) IsStop = true; end |
require("QluaCSharpConnector") IsStop = false; -- Флаг остановки скрипта Stack = {}; -- Массив для стека Stack.idx_for_add = 1; -- Индекс для добавления следующей, или первой записи Stack.idx_for_get = 1; -- Индекс для изъятия следующей, или первой записи Stack.count = 0; -- Количество находящихся в стеке записей Stack.max = 1000; -- Максимально возможное количество записей в стеке (при переполнении старые записи будут замещаться новыми) CLASS_CODE = "SPBFUT"; -- Класс бумаги SEC_CODE = "RIH5"; -- Код бумаги function main() local FirstQuote = true; local Quote = ""; -- ОСНОВНОЙ ЦИКЛ while not IsStop do -- если робот получил СТАКАН, или это первый СТАКАН if QluaCSharpConnector.CheckGotQuote() or FirstQuote then -- берет стакан из стека Quote = GetFromStack(); -- если стек не пустой if Quote ~= nil then if FirstQuote then FirstQuote = false; end; -- отправляет стакан роботу QluaCSharpConnector.SendQuote(Quote); end; end sleep(1); end; end -- Добавляет запись в стек function AddToStack(NewEntry) -- Добавляет запись в стек Stack[Stack.idx_for_add] = NewEntry; -- Корректирует счетчик находящихся в стеке записей if Stack.count < Stack.max then Stack.count = Stack.count + 1; end; -- Увеличивает индекс для добавления следующей записи Stack.idx_for_add = Stack.idx_for_add + 1; -- Если индекс больше максимально допустимого, то следующая запись будет добавляться в начало стека if Stack.idx_for_add > Stack.max then Stack.idx_for_add = 1; end; -- Если изъятие записей отстало от записи (новая запись переписала старую), то увеличивает индекс для изъятия следующей записи if Stack.idx_for_add - Stack.idx_for_get == 1 and Stack.count > 1 -- смещение внутри стека then Stack.idx_for_get = Stack.idx_for_get + 1; -- Добавил в конец, когда индекс для изъятия тоже был в конце и количество не равно 0 elseif Stack.idx_for_get - Stack.idx_for_add == Stack.max - 1 and Stack.count > 1 then Stack.idx_for_get = 1; end; end; -- Извлекает запись из стека function GetFromStack() local OldInxForGet = Stack.idx_for_get; if Stack.count == 0 then return nil; end; -- Уменьшает количество записей на 1 Stack.count = Stack.count - 1; -- Корректирует, если это была единственная запись if Stack.count == -1 then Stack.count = 0; Stack.idx_for_get = Stack.idx_for_add; -- Выравнивает индексы else -- Если еще есть записи -- Сдвигает индекс изъятия на 1 вправо Stack.idx_for_get = Stack.idx_for_get + 1; -- Корректирует, если достигнут конец if Stack.idx_for_get > Stack.max then Stack.idx_for_get = 1; end; end; return Stack[OldInxForGet]; end; --- Функция вызывается терминалом QUIK при получении изменения стакана котировок function OnQuote(class, sec ) if class == CLASS_CODE and sec == SEC_CODE then -- Получает стакан по нужному инструменту ql2 = getQuoteLevel2(class, sec); -- Представляет снимок СТАКАНА в виде СТРОКИ QuoteStr = ""; for i = tonumber(ql2.bid_count), 1, -1 do if ql2.bid[i].quantity ~= nil then QuoteStr = QuoteStr..tostring(tonumber(ql2.bid[i].quantity))..";"..tostring(tonumber(ql2.bid[i].price))..";"; else QuoteStr = QuoteStr.."0;"..tostring(tonumber(ql2.bid[i].price))..";"; end; end; for i = 1, tonumber(ql2.offer_count), 1 do if ql2.offer[i].quantity ~= nil then if i < tonumber(ql2.offer_count) then QuoteStr = QuoteStr..tostring(tonumber(ql2.offer[i].quantity))..";"..tostring(tonumber(ql2.offer[i].price))..";"; else QuoteStr = QuoteStr..tostring(tonumber(ql2.offer[i].quantity))..";"..tostring(tonumber(ql2.offer[i].price)); end; else if i < tonumber(ql2.offer_count) then QuoteStr = QuoteStr.."0;"..tostring(tonumber(ql2.offer[i].price))..";"; else QuoteStr = QuoteStr.."0;"..tostring(tonumber(ql2.offer[i].price)); end; end; end; -- Добавляет СТАКАН-строку в стек AddToStack(QuoteStr); end; end; --- Функция вызывается терминалом QUIK при завершении пользователем скрипта function OnStop(s) IsStop = true; end
// Имя для выделенной памяти TCHAR Name[] = TEXT("TerminalQuote"); // Создаст, или подключится к уже созданной памяти с таким именем HANDLE hFileMapTerminalQuote = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1400, Name); //Проверяет получил-ли робот последний СТАКАН static int forLua_CheckGotQuote(lua_State *L) { //Если указатель на память получен if (hFileMapTerminalQuote) { //Получает доступ к байтам памяти PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapTerminalQuote, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 1400)); //Если доступ к байтам памяти получен if (pb != NULL) { //проверяет на пустую запись (сигнал, что можно отправлять стакан) if (pb[0] == 0) { lua_pushboolean(L, true); } else { lua_pushboolean(L, false); } //закрывает представление UnmapViewOfFile(pb); } else lua_pushboolean(L, false); } else { lua_pushboolean(L, false); } return(1); } //Отправляет новые изменения стакана static int forLua_SendQuote(lua_State *L) { //Если указатель на память получен if (hFileMapTerminalQuote) { //Получает доступ к байтам памяти PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapTerminalQuote, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 1400)); //Если доступ к байтам памяти получен if (pb != NULL) { //Получает из Lua-стека переданное значение const char *Quote = lua_tostring(L, 1); int Size = 0; //считает количество символов в строке for (int i = 0; i < 1400; i++) { if (Quote[i] == 0)break; Size++; } //записывает стакан в память memcpy(pb, Quote, Size); //lua_pushstring(L, (char*)pb);//возвращает то, что записалось (если раскомментировать) (может пригодиться при отладке) //закрывает представление UnmapViewOfFile(pb); } else lua_pushstring(L, ""); } else lua_pushstring(L, ""); return(1); } |
// Имя для выделенной памяти TCHAR Name[] = TEXT("TerminalQuote"); // Создаст, или подключится к уже созданной памяти с таким именем HANDLE hFileMapTerminalQuote = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1400, Name); //Проверяет получил-ли робот последний СТАКАН static int forLua_CheckGotQuote(lua_State *L) { //Если указатель на память получен if (hFileMapTerminalQuote) { //Получает доступ к байтам памяти PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapTerminalQuote, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 1400)); //Если доступ к байтам памяти получен if (pb != NULL) { //проверяет на пустую запись (сигнал, что можно отправлять стакан) if (pb[0] == 0) { lua_pushboolean(L, true); } else { lua_pushboolean(L, false); } //закрывает представление UnmapViewOfFile(pb); } else lua_pushboolean(L, false); } else { lua_pushboolean(L, false); } return(1); } //Отправляет новые изменения стакана static int forLua_SendQuote(lua_State *L) { //Если указатель на память получен if (hFileMapTerminalQuote) { //Получает доступ к байтам памяти PBYTE pb = (PBYTE)(MapViewOfFile(hFileMapTerminalQuote, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 1400)); //Если доступ к байтам памяти получен if (pb != NULL) { //Получает из Lua-стека переданное значение const char *Quote = lua_tostring(L, 1); int Size = 0; //считает количество символов в строке for (int i = 0; i < 1400; i++) { if (Quote[i] == 0)break; Size++; } //записывает стакан в память memcpy(pb, Quote, Size); //lua_pushstring(L, (char*)pb);//возвращает то, что записалось (если раскомментировать) (может пригодиться при отладке) //закрывает представление UnmapViewOfFile(pb); } else lua_pushstring(L, ""); } else lua_pushstring(L, ""); return(1); }
// Создаст, или подключится к уже созданной памяти с таким именем public MemoryMappedFile MemoryTerminalQuote; // Создает поток для чтения StreamReader SR_TerminalQuote; // Создает поток для записи StreamWriter SW_TerminalQuote; //Флаг работы приложения (аналогично QLua) //Ключевое слово "volatile" гарантирует, что в переменной в любое время и при обращении из любого потока будет актуальное значение public volatile bool Run = true; |
// Создаст, или подключится к уже созданной памяти с таким именем public MemoryMappedFile MemoryTerminalQuote; // Создает поток для чтения StreamReader SR_TerminalQuote; // Создает поток для записи StreamWriter SW_TerminalQuote; //Флаг работы приложения (аналогично QLua) //Ключевое слово "volatile" гарантирует, что в переменной в любое время и при обращении из любого потока будет актуальное значение public volatile bool Run = true;
…
//выделяет именованную память под получение СТАКАНА от QUIK, создает потоки чтения/записи MemoryTerminalQuote = MemoryMappedFile.CreateOrOpen("TerminalQuote", 1400, MemoryMappedFileAccess.ReadWrite); SR_TerminalQuote = new StreamReader(MemoryTerminalQuote.CreateViewStream(), System.Text.Encoding.Default); SW_TerminalQuote = new StreamWriter(MemoryTerminalQuote.CreateViewStream(), System.Text.Encoding.Default); |
//выделяет именованную память под получение СТАКАНА от QUIK, создает потоки чтения/записи MemoryTerminalQuote = MemoryMappedFile.CreateOrOpen("TerminalQuote", 1400, MemoryMappedFileAccess.ReadWrite); SR_TerminalQuote = new StreamReader(MemoryTerminalQuote.CreateViewStream(), System.Text.Encoding.Default); SW_TerminalQuote = new StreamWriter(MemoryTerminalQuote.CreateViewStream(), System.Text.Encoding.Default);
…
//Считывает стакан из памяти private string GetTerminalQuoteData() { SR_TerminalQuote.BaseStream.Seek(0, SeekOrigin.Begin); return SR_TerminalQuote.ReadToEnd().Trim('\0', '\r', '\n'); } //Очищает память, сообщая тем самым терминалу, что стакан получен private void SetTerminalQuoteData(string Data = "") { SW_TerminalQuote.BaseStream.Seek(0, SeekOrigin.Begin); for (int i = 0; i < 1400; i++) SW_TerminalQuote.Write("\0"); if (Data != "") { SW_TerminalQuote.BaseStream.Seek(0, SeekOrigin.Begin); SW_TerminalQuote.Write(Data); } SW_TerminalQuote.Flush(); } |
//Считывает стакан из памяти private string GetTerminalQuoteData() { SR_TerminalQuote.BaseStream.Seek(0, SeekOrigin.Begin); return SR_TerminalQuote.ReadToEnd().Trim('\0', '\r', '\n'); } //Очищает память, сообщая тем самым терминалу, что стакан получен private void SetTerminalQuoteData(string Data = "") { SW_TerminalQuote.BaseStream.Seek(0, SeekOrigin.Begin); for (int i = 0; i < 1400; i++) SW_TerminalQuote.Write("\0"); if (Data != "") { SW_TerminalQuote.BaseStream.Seek(0, SeekOrigin.Begin); SW_TerminalQuote.Write(Data); } SW_TerminalQuote.Flush(); }
…
//Получает снимок СТАКАНА из QUIK private void GettingQuoteData() { //Запускает функцию получения стакана в отдельном потоке, чтобы приложение откликалось на действия пользователя //Чтобы остановить выполнение данного потока, нужно переменной "Run" присвоить значение "false" //Сделать это можно, либо в функции по событию закрытия приложения, либо при нажатии на кнопке и т.д. new Thread(() => { string QuoteStr = ""; string[] QuoteStrParts; //Постоянный цикл в отдельном потоке while (Run) { //Получает стакан из памяти QuoteStr = GetTerminalQuoteData(); //Если СТАКАН получен, стирает запись, подтверждая это if (QuoteStr != "00" && QuoteStr != "" && QuoteStr != "0" && QuoteStr != "-1") { //Стирает запись, подтверждая что стакан получен и будет обработан SetTerminalQuoteData(); //Разделяет снимок СТАКАНА на составляющие QuoteStrParts = QuoteStr.Split(';'); //Что-то делает с полученным стаканом // ... //Удаляет массив QuoteStrParts = null; } //Чтоб процесс не "забивал" одно из ядер процессора на 100% нужна пауза в 1 миллисекунду Thread.Sleep(1); } }).Start(); } |
//Получает снимок СТАКАНА из QUIK private void GettingQuoteData() { //Запускает функцию получения стакана в отдельном потоке, чтобы приложение откликалось на действия пользователя //Чтобы остановить выполнение данного потока, нужно переменной "Run" присвоить значение "false" //Сделать это можно, либо в функции по событию закрытия приложения, либо при нажатии на кнопке и т.д. new Thread(() => { string QuoteStr = ""; string[] QuoteStrParts; //Постоянный цикл в отдельном потоке while (Run) { //Получает стакан из памяти QuoteStr = GetTerminalQuoteData(); //Если СТАКАН получен, стирает запись, подтверждая это if (QuoteStr != "00" && QuoteStr != "" && QuoteStr != "0" && QuoteStr != "-1") { //Стирает запись, подтверждая что стакан получен и будет обработан SetTerminalQuoteData(); //Разделяет снимок СТАКАНА на составляющие QuoteStrParts = QuoteStr.Split(';'); //Что-то делает с полученным стаканом // ... //Удаляет массив QuoteStrParts = null; } //Чтоб процесс не "забивал" одно из ядер процессора на 100% нужна пауза в 1 миллисекунду Thread.Sleep(1); } }).Start(); }