VPM, Возможно я не понял, вопроса, Но так как Вы возвращаетесь к этой идеи уже многократно, то поясню подробнее в чем у Вас ошибка. --------------------- Во-первых, в терминологии. Вы уж не обижайтесь, но прежде, чем обсуждать надо быть уверенным , что мы с вами горшками называем горшки, а цветами -цветы. ------------------ Так вот, Стек и очередь это две большие разницы. Стек - это кипа - т е представьте кипу листов бумаги на столе. Вы положили лист сверху и взяли его. -------------------- или еще пример стека - это обойма в автомате патрон лежащий сверху выйдет первым. ----------------- Т е принцип последним зашел -первым вышел. ------------------------- Очередь -это первый зашел и первым купил. Наглядный пример - очередь в магазине за яйцами. ------------------------ Поэтому невозможно стек сделать из очереди. Если конечно Вы не сторонник смотреть гланды через зад. ==================== Но это не главное. ======================== Давайте посмотрим на то как вы пытаетесь уменьшить память, создав нечто, в котором Вы синхронно выкидываете первый элемент и запихиваете последний. ---------------------------- Ничего это вам не напоминает? А посмотрите эту мою тему Про сдвиговые регистры. Именно это и происходи в них. -------------------- Но они реализуются методами, которые я описал выше. =================== Если же Вы будете просто в массиве луа стирать первый элемент, записывая туда nil и добавлять в конец новый, то массив будет увеличиваться на этот новый элемент. ---------------------------------------- В луа для хранения одного числа в таблице тратится 12 байт. Когда Вы пишите nil в первый элемент, то память из под него не освобождается, так как ее невозможно удалить из того блока памяти, который уже выделен для этого массива. ------------------- В лучшем случае освободится память на элемент массива, если он является тоже таблицей. ====================== вот ваш вариант.
Код
---- Операции вставки -- table.insert
function List.pushlast (list, value) local last = list.last + 1; list.last = last; list[last] = value; end
---- Операции удаления -- table.remove
function List.popfirst (list)
local first = list.first; if first > list.last then return nil end
local value = list[first] list[first] = nil list.first = first + 1 return value end
list[last] = value; -- этот оператор записывает в таблицу новое значение. -------------------------------- При создании таблицы , ей выделяется место в оперативной памяти. ---------------------------------- Если при записи нового значения места не хватает, то оно будет увеличено. ----------------------- C точки зрения сборки мусора. Объектом для сборки является вся таблица, а не отдельная ее ячейка. ================ Когда Вы в функции popfirst делаете это: list[first] = nil то в ячейке таблицы в параметр тип элемента записывается ноль. Но память для хранения этого типа и значения элемента не может быть освобождена, так как она не является самостоятельным объектом. Это часть память таблицы. ======================= Таким образом, в вашем варианте таблица может только увеличивать объем занимаемой памяти, но не уменьшать ее. ========================= Уменьшать, можно лишь так, как я рассказал в этой теме.
bespalex написал: Уникальность номеров заявок order_num поддерживается только внутри одного торгового дня. Также как и trade_num для сделок. Получается при использовании этих параметров в качестве индекса может возникнуть ситуация, что записи имеющиеся совпадут по индексу с новыми заявками и сделками дня, что может привести к труднопредсказуемым последствиям. Использовать trans_id в качестве уникального индекса тоже ненадежный способ, т.к. логика его передачи в сообщениях нестабильна. Также например случаются существенные задержки с поступлением OnTransReply и тогда невозможно отменить заявку, т.к. для этого необходим order_num, которого у нас еще может не быть. Кто как решает эту проблему?
Внутри дня используем номе сделки А между днями - дополнительно дату. Устроит такое решение?
VPM написал: nikolz, Вы можете сравнить это метод с local lifo=List.new() List.pushlast(lifo,i) List.popfirst(lifo), и где какой использовать лучше или предпочтительней
Я уже сравнивал и вам в вашей теме выложил результат. Вы очевидно его не читали. ------------ Если кратко, то Ваш вариант ничего не экономит. Вы посмотрите там результат работы сборщика и пояснение почему Вы заблуждаетесь с этим вариантом. -------------------- Если будет непонятно, то поясню дополнительно.
Добрый день, В этой теме я расскажу кратко что такое сдвиговые регистры и циклические массивы. И покажу как на Lua экономить память при создании роботов. ---------------------- При торговле на бирже приходит много информации, такой , как новости, результаты сделок, значения индикаторов, например свечей. эта информация записывается в массивы и в файлы и может составлять очень большой объем. ------------------ Однако, при реальной торговле, для принятия решения, нет надобности не только во всей накопленной информации в файлах, но и порою нужна лишь информация за последние например 60 минут, свечей с интервалом 1 минута. ================= Чтобы массивы с данными не разбухали до безумных размеров я реализую временное окно, в пределах которого храню текущую информацию. ------------------- Например, храним только 1024 последних отсчета. Для этого необходимо выделить массив всего в 1024 элемента. Для каждого нового отсчета необходимо сдвинуть содержимое массив влево на один отсчет, а в освободившейся место записать принятое значение. -------------------- Возникает вопрос как реализовать этот сдвиг элементов массива, чтобы было быстро. --------------------- Один из приемов - циклические массивы. ------------------- В таком массиве элементы не двигаются, а двигается указатель на место удаляемого первого элемента . Это же место является местом размещения принятого элемента. --------------- Еще одним способом является использование многобитовых регистров процессора, с помощью которых можно реализовать многобитовые сдвиги данных за одну операцию. =================== В луа есть операции сдвига >>m (<<m). Они позволяют сдвинуть целое число на m разрядов влево ( вправо), что соответствует делению(умножению) этого числа на 2 в степени m. Такая операция сдвига числа выполняется в регистре сдвига ( в процессоре это АЛУ+регистры) ------------------ В современных процессорах Intel есть регистры в 256 бит (в последних 512 бит) ------------------------------ Так как число double занимает 64 бита, а long(integer в lua) 32 бита, то с помощью этих регистров можно сдвигать не одно число, а массив из 4,8,16 чисел одной командой. Для простоты будем называть эти регистры 256(512) ,битовыми тоже сдвиговыми регистрами. ------------------ Очевидно, что с помощью этих регистров можно сдвигать массивы чисел. ======================= Если не использовать С for lua, то в скрипте на луа можно реализовать два способа работы с такими временными окнами. ------------------------------ Либо циклический массив, либо сдвиг элементов массива. -------------------------------------- Так как данный ликбез в основном для начинающих, то покажу наиболее простой способ, но при этом достаточно быстродействующий. ------------------------------- Вот скрипт функций, которые реализуют механизм такого временного окна данных, где N - размер временного окна
Код
local function newCA(N) local t={}; for j=1,N do t[j]=0; end; return t; end --вставка нового элемента
local function setCA(t,X) table.remove(t,1); t[#t+1]=X; end --вставка нового элемента
Посмотрим, что сохраняется в таком массиве Организуем цикл записи номеров отсчетов которые изменяются от 1 до M и какие из отсчетов будут сохраняться в массиве фиксированной длины 32 элемента
Код
local M=1000000
local t=newCA(32)
for j=1,M do setCA(t,j)
print("j="..j..",t="..table.concat(t,","));
end
Оценим быстродействие такого метода для массива в 1024 элемента
Код
local M=1000000
local t=newCA(1024)
--nklib.startA();
local tim=os.clock();
for j=1,M do setCA(t,j) end
--local tim=0.1*nklib.stopA()/M
tim=os.clock()-tim
print(1000000.*tim/M.." мкс" )
Т .е. на сохранение одного нового элемента в таком массиве затрачивается примерно 10 мкс. --------------------- Таким образом, с помощью приведенных выше двух простых функций, Вы можете управлять расходом оперативной памяти в своем роботе, исходя из требуемого временного окна обработки поступающих данных.
В этой теме https://forum.quik.ru/forum17/topic8467/ я подробно рассказал как просто сделать очередь событий в роботе и для чего это нужно. При этом использование функции sleep не приводит к потере событий на рынке. ----------------------------- Однако, такое решение имеет недостаток в том, что ядро процессора простаивает, когда в очереди есть события. ----------------------- Может показаться, что простаивание ядра на 10 ms это мелочь. Но у нас ядро работает на частоте 3ГГц и более. За 10ms процессор может выполнить более 100 тысяч команд. Стоит ли их терять? --------------------- Я использую другой способ остановки процессора на время отсутствия событий. Рассказываю о нем лишь в самом упрощенном виде. Кто пожелает его реализовать, рекомендую книгу Дж.Рихтера. -------------------- Этот механизм можно реализовать на СИ. Для этого создается специальный флаг, называемый event - событие. Для обнаружения события используется функция ожидания события. С учетом этого у меня колбеки записаны иначе, чем в указанной выше теме. В них добавлена функция управления событием. А в функцию main добавлена функция ожидания, которая дополнительно реализует функцию "сторожевого пса" Вот фрагменты этих участков скрипта:
Код
function OnParam(c,s) _N=_N+1; tpr[_N]={14,c,s}; nkevent.Set(event);end
Код
----
local w=nkevent.wait(); --ждем события
if w~=0 then Log:write(w..",прошло 30 секунд\n"); Log:flush(); end
while _N>0 do
----
Glukator написал: nikolz, объясняльщик ты х**в, понятие "сдвиговый регистр" пришло в ЦОС из схемотехники, где оно материализуется в виде законченного устройства, в виде микросхемы, естественно, с ограниченным числом ячеек. А "очередь" - понятие чисто алгоритмическое, подразумевающее добавление элементов с одного конца, а извлечение с другого, вот и все. И по барабану, с какого там конца программист индексы нумерует. И то, что ему захотелось длину очереди ограничить - с алгоритмической точки зрения тоже ниего не меняет, кто раньше вошел - тот раньше вышел. Называй как хочешь, суть не меняется. Терминологии он тут решил поучить, ага.
А ты тогда просто мудак. Объясняю для тебя специально. В цифровой обработке сигналов есть понятие элементы задержки. Эти понятия есть даже в ИИ. И это вполне алгоритмическое понятие в настоящее время, так как большинство разработчиков ИИ понятия не имеют о схемотехнике. Так вот , то что хочет автор этой темы реализуется не очередями и не стеками, а циклическими массивами. ----------------------- Да и еще Даже в луа есть операция сдвига вправо и влево - а это и есть сдвиговый регистр . можешь придумать другое название Мне по... А то что Вы здесь херней занимаетесь это факт.
TGB написал: При перезаписи, если нет других ссылок на область памяти, то она будет удалена (почищена) мусорщиком.
Это значит, что память расти не будет.
Херня это все У вас очередь - это массив. Если в массиве хранятся числа, то для них не отводится дополнительной памяти. Но даже если Вы храните указатель, то сам элемент массива останется в памяти. Запись nil - это запись 0 в тип элемента массива. Элементы массива никуда не денутся пока Вы не уничтожите весь массив путем записи nil в его имя. а не в его элементы.
VPM, Вам тут Виктор засрал мозги своей "гениальной" программой очереди. Вот Вам тест, который доказывает, что ничего это творение не экономит. Объясняю что делает тест. Сначала в цикле загоняются push числа в очередь и очередь увеличивается Потом все числа выгоняются из очереди функцией pop и очередь становится вообще пустая ---------------- В процессе этого измеряем расход памяти до и после и запускаем сборщик чтобы собрать все что освободилось вот сам тест:
Код
local M=10000000
local count1 = collectgarbage("count") -- получаем текущий размер памяти
print("начальный объем занятой памяти",count1);
--------------------------
local list=List.new()
for i=1, M do List.pushlast(list,i) end
print("число элементов в очереди list после "..M.." циклов функции push="..#list)
local count1 = collectgarbage("count")
print("объем занятой памяти",count1);
--------------------------------
for i=1, M do List.popfirst(list) end
print("число элементов в очереди list после "..M.." циклов pop="..#list)
local count1 = collectgarbage("count")
print("объем занятой памяти после сборщика мусора",count1);
collectgarbage("collect")
local count1 = collectgarbage("count") -- получаем текущий размер памяти
print("объем занятой памяти после pop",count1);
------------------------------
collectgarbage("collect") -- выполняем сбор мусора
local count2 = collectgarbage("count") -- получаем размер памяти после сбора
print("объем занятой памяти после сборщика мусора",count1);
а это результат:
Код
>D:/lua53/lua53.exe -e "io.stdout:setvbuf 'no'" "Test_List.lua"
начальный объем занятой памяти 63.69921875
число жлементов в очереди list после 10000000 циклов функции push=9999999
объем занятой памяти 262202.31738281
число элементов в очереди list после 10000000 циклов pop=0
объем занятой памяти после сборщика мусора 262202.51953125
объем занятой памяти после pop 262202.12792969
объем занятой памяти после сборщика мусора 262202.12792969
>Exit code: 0
Как видим, ничего не освободилось. НОЛЬ. При этом элементов в очереди тоже ноль. ============================ Ты не поверь, Карл, такую чушь обсуждают целую неделю.
Объясняю медленно . ----------------------- Для экономии памяти очередь вообще не причем. В цифровой обработке сигналов нет даже такого понятия. Для этой цели применяются сдвиговые регистры (массивы, вектора) -------------------- Это КЛАССИКА в цифровой обработке сигналов. ------------------- Все фильтры и индикаторы строятся на циклических массивах. ------------------
Функция выдает нули. Что не так? ------------- Тест:
Код
function main()
while fconnect do
if getInfoParam("SERVERTIME") then fconnect=2; end
while fconnect==2 do
c="QJSIM"; s="SBER" client="10326"; acc="NL0011100043"; price=275.74;
qty,com=CalcBuySell(c,s,client,acc,price,true,false);
local s=c..","..s..","..tostring(client)..","..tostring(price)..","..tostring(acc)..", qty="..tostring(qty);
message(s,1);
sleep(10000); end
end
end
function OnInit(pfile) fconnect=isConnected(); end
Добрый день, Вопрос к разработчикам В документации на библиотеку QLua заявлено, что os.sysdate() возвращает системное время с точность до микросекунды. ------------------------ Это означает, как я понимаю, что если я получу с помощью этой функции время в двух точках скрипта, то разница даст мне время исполнения этого участка с точностью до микросекунды. ------------------ Я написал такой тест:
Код
paths = "D:/nkarray/"
package.cpath =paths.."?.dll";
require "nkarray"
event=nkevent.Create("event");
---------------------
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then fconnect=2; end
------------------------------
while fconnect==2 do
w=nkevent.wait(event); --ждем события
while _N>0 do
t=_tN[_N]; _N=_N-1
----------------------
tim2=0.1*nklib.stopA();
T=os.sysdate();
local _,_,_,t3=nkvm.D(); --HMS,YMD,dHMS,dt,ns
t1=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs);
local x1=1000000.*(t1-t[1])//1;
local x2=1000000.*(t3-t[2])//1;
Log:write("os.sysdate OnParam(сек)="..t[1]..", os.sysdate main(сек)="..t1..",разность 1(мкс)=="..(t1-t[1]).."\n");Log:flush();
Log:write("nkvm.D OnParam(мкс)="..t[2]..", nkvm.D main(мкс)="..t3..", разность 2(мкс)="..(t3-t[2]).."\n");Log:flush();
Log:write("разность 3 по высокоточному таймеру OC(мкс)="..tim2.."\n\n");Log:flush();
end
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s)
local T=os.sysdate(); local tim=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs)
nklib.startA(); local _,_,_,tim1=nkvm.D(); _N=_N+1; _tN[_N]={tim,tim1};
nkevent.Set(event);
end
В котором я замеряю системное время в колбеке on Param в строке
Код
local T=os.sysdate(); local tim=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs)
и передаю его в функцию main в функции main я снова получаю системное время в строках
и все три значения вывожу в лог файл, который покажу ниже. ---------------------- кроме того, для сравнения я делаю тоже самое на основе собственной функции времени а также своего таймера который реализован на высокоточном таймере процессора. Но это так, чтобы показать, что действительность не такая, как показывает ваша функция. и вот результат :
На основе Вашей функции разность показаний равна НУЛЮ. Мои функции показывают разность от 63 до 24 мкс, что более соответствует реальному процессу, так как процессор у меня далеко не квантовый. -------------------- Вопрос: Что не так с вашей функцией os.sysdate() ?
VPM написал: Nikolay, Ну первый вопрос зачем лезть в глобальное окружение, я видел у Вас такой прием?
Поясняю. Основной стек VMLua в КВИКЕ - это то, что вне функции main. Функция main - создана как coroutines (вы это уже знаете) , но только в отдельном потоке. -------------------- У main, как у coroutines , есть доступ к переменных основной VMLua. Этот доступ через глобальные функции. Все библиотеки в том числе QLUA содержатся в глобальной таблице, поэтому к ним есть доступ из main, но лишь как к глобальным. -------------------
Serge123 написал: Кто-то может рассказать об удачной схеме работы торговой программы (без дорогих сердцу торговых стратегий), чтобы потом не переделывать своё изобретение? Я пока не знаю, как это всё в сумме организовывать и с какой стороны подходить... Торговать лучше по индикаторам или как-то ещё?
Примечательно то, что наличие очереди дает нам возможность получить и обработать все события, которые принимают колбеки вне зависимости от величины времени бездействия функции sleep. ---------------- Если бы я не использовал очередь, то в случае sleep(1000) - 1 секунда, в каждом цикле терялись несколько десятков событий. Если посмотрите тему "мой робот", то там видно что за 5 секунд приходит до 3000 событий, которые без организации очереди просто потеряются. ===================== А теперь я объясню как в моем скрипте организована очередь и почему она такая простая. --------------- Очередь реализована так. строка:
Код
_N=0; _tN={};
в ней задается счетчик элементов очереди _N и таблица для этих элементов ------------------- запись элемента в очередь производится в колбеках всегда одинаково просто:
Код
_N=_N+1; _tN[_N]={1,t};
а для колбека onParam так:
Код
_N=_N+1; _tN[_N]={99,c,s,tim};
т е увеличиваем счетчик элементов и записываем в таблицу по значению счетчика ключ и передаваемые параметры. ----------------------- При записи не требуется никакой синхронизации, так как запись лишь в колбеках, а они в одном потоке. ---------------------- Чтение и удаление элемента из очереди реализована в функции main так:
Код
t=_tN[_N]; _N=_N-1
Элемент не удаляется, а уменьшается лишь счетчик. Удаление элемента произойдет лишь, если на его место будет записан новый элемент в колбеке. ===================== Вот и вся очередь. ----------------------- Все просто , но весьма эффективно. --------------------- Далее я расскажу как сделать обработку еще быстрее, но не сложнее. ----------------- Продолжение возможно следует..
В данном варианте скрипта у нас нет информации о времени исполнения каких-либо фрагментов. изменим скрипт и добавим измерения времени исполнения. кроме того, начнем процесс изучения с классической рекомендации разработчиков - поставить sleep на 1 секунду вот такой скрипт:
Код
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then
DT=getInfoParam("TRADEDATE"); DT=tonumber(string.sub (DT,7,10)..string.sub (DT,4,5)..string.sub (DT,1,2));
fconnect=2;
Log:write("DT="..tostring(DT)..",fconnect="..fconnect.."\n");Log:flush();
end
------------------------------
local clas,sec,ncb,t,_time,tim1,tim2,T;
local tim0,tim1,tim2,T;
-----------------------------
while fconnect==2 do
while _N>0 do
t=_tN[_N]; _N=_N-1 ncb=t[1];
----------------------
if ncb==99 then
clas,sec=t[2],t[3]; tim1=t[4];
else
t=t[2];
if ncb==1 then
clas=t.class_code; sec=t.sec_code; --может не быть
elseif ncb==2 then
clas=t.class_code; sec=t.sec_code;
end
end
T=os.sysdate(); tim=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs);
tim=(1000000.*(tim-tim1))//1;
Log:write(ncb..",tim="..tim..",clas="..tostring(clas)..",sec="..tostring(sec)..",_N=".._N.."\n");Log:flush();
end
sleep(1000);
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s)
local T=os.sysdate(); local tim=60.*(60.*T.hour+T.min)+T.sec+0.001*(T.ms+0.001*T.mcs)
_N=_N+1; _tN[_N]={99,c,s,tim};
end
function OnTransReply(t) _N=_N+1; _tN[_N]={1,t}; end
function OnOrder(t) _N=_N+1; _tN[_N]={2,t}; end
В лог файле есть два значения времени tim, - задержка исполнения колбека onParam в main Как видно из лог файла , Как видим из лог файла очередь на обработку никогда не бывает пустой. Задержка составляет от 0.3 до 0.8 сек. В лог файле она в мкс. -------------------------- функция sleep установлена чтобы уменьшить загрузку ядра процессора холостыми циклами. Поэтому посмотрим в диспетчере задач эту загрузку. Она составляет:
Примерно 8.5% теперь давайте уменьшим эту величину до 1 ms в результате получим:
Тем, кто хотел бы практически разобраться в данном вопросе, рекомендую установить демо КВИК и запускать скрипты, которые я буду выкладывать и пояснять. Итак начнем. Вот исходный скрипт, в нем закомментированы некоторые операторы, которые демонстрируют однотипность записи колбеков и их обработку в функции main. В первых трех строчках скрипта создаемся лог файл, куда скрипт выводит информацию о своей работе. Вам надо исправить 2-ю строку, записав в ней путь, где Вы хотите создать этот файл
Код
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then
DT=getInfoParam("TRADEDATE"); DT=tonumber(string.sub (DT,7,10)..string.sub (DT,4,5)..string.sub (DT,1,2));
fconnect=2;
Log:write("DT="..tostring(DT)..",fconnect="..fconnect.."\n");Log:flush();
end
------------------------------
local clas,sec,ncb,t,_time;
-----------------------------
while fconnect==2 do
while _N>0 do
t=_tN[_N]; _N=_N-1 ncb=t[1];
----------------------
if ncb==99 then
clas,sec=t[2],t[3];
else
t=t[2];
if ncb==1 then
clas=t.class_code; sec=t.sec_code; --может не быть
elseif ncb==2 then
clas=t.class_code; sec=t.sec_code;
-- elseif ncb==3 then
-- elseif ncb==4 then
-- elseif ncb==5 then
-- elseif ncb==6 then
-- elseif ncb==7 then
-- elseif ncb==8 then
-- elseif ncb==9 then
-- elseif ncb==10 then
-- elseif ncb==11 then
-- elseif ncb==12 then
end
end
Log:write(ncb..",clas="..tostring(clas)..",sec="..tostring(sec)..",_N=".._N.."\n");Log:flush();
_time=0;
end
sleep(1);
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s) _N=_N+1; _tN[_N]={99,c,s,tim};end
---------------------
function OnTransReply(t) _N=_N+1; _tN[_N]={1,t}; end
function OnOrder(t) _N=_N+1; _tN[_N]={2,t}; end
--function OnStopOrder(t) _N=_N+1; _tN[_N]={3,t}; end
--function OnTrade(t) _N=_N+1; _tN[_N]={4,t}; end
--function OnQuote(c,s) _N=_N+1; _tN[_N]={5,t}; end
--function OnAllTrade(t) _N=_N+1; _tN[_N]={6,t}; end
--function OnDepoLimit(t) _N=_N+1; _tN[_N]={7,t}; end
--function OnAccountBalance(t) _N=_N+1; _tN[_N]={8,t}; end-- изменение позиции по счету
--function OnAccountPosition(t) _N=_N+1; _tN[_N]={9,t}; end-- изменение позиции по счету
--function OnDepoLimit(t) _N=_N+1; _tN[_N]={10,t}; end -- изменение позиции по инструментам
--function OnFuturesClientHolding(t) _N=_N+1; _tN[_N]={11,t}; end -- изменение позиции по срочному рынку
--function OnMoneyLimit(t) _N=_N+1; _tN[_N]={12,t}; end -- изменение денежной позиции
--function OnClose() fconnect=nil end
--function OnConnected(flag) fconnect=1; end
--function OnDisconnected() fconnect=1 end
--function OnStop(flag) fconnect=nil; return 2000 end
--function OnCleanUp() end -- смена торговой сессии
--function OnDepoLimitDelete(t) end -- удаление позиции по инструментам
--function OnFirm(t) end -- получение описания новой фирмы
--function OnFuturesLimitChange(t) end -- изменение ограничений по срочному рынку
--function OnFuturesLimitDelete(t) end -- удаление лимита по срочному рынку
--function OnMoneyLimitDelete(t) end -- удаление денежной позиции
--function OnNegDeal end-- новая заявка на внебиржевую сделку или изменение параметров существующей заявки на внебиржевую сделку
--function OnNegTrade end-- новая сделка для исполнения или изменение существующей сделки для исполнения
для наглядности уберем эти комментарии:
Код
name="bot"; -- имя робота
path = "D:/QUIK_SCRIPT/nk_bot/" --папка где разместим лог файл
Log=io.open(path..name..".log","w") -- создаем лог файл для записи
------------
_N=0; _tN={};
----------------------
function main()
-----------------------
while fconnect do
if getInfoParam("SERVERTIME") then
DT=getInfoParam("TRADEDATE"); DT=tonumber(string.sub (DT,7,10)..string.sub (DT,4,5)..string.sub (DT,1,2));
fconnect=2;
Log:write("DT="..tostring(DT)..",fconnect="..fconnect.."\n");Log:flush();
end
------------------------------
local clas,sec,ncb,t,_time;
-----------------------------
while fconnect==2 do
while _N>0 do
t=_tN[_N]; _N=_N-1 ncb=t[1];
----------------------
if ncb==99 then
clas,sec=t[2],t[3];
else
t=t[2];
if ncb==1 then
clas=t.class_code; sec=t.sec_code; --может не быть
elseif ncb==2 then
clas=t.class_code; sec=t.sec_code;
end
end
Log:write(ncb..",clas="..tostring(clas)..",sec="..tostring(sec)..",_N=".._N.."\n");Log:flush();
_time=0;
end
sleep(1);
end
end
end
----------------------------
function OnInit(pfile) fconnect=isConnected(); end
----------------
function OnParam(c,s) _N=_N+1; _tN[_N]={99,c,s,tim};end
---------------------
function OnTransReply(t) _N=_N+1; _tN[_N]={1,t}; end
function OnOrder(t) _N=_N+1; _tN[_N]={2,t}; end
Запустим этот скрипт на исполнение и получим следующий результат в лог файле
Добрый день, Хочу поделиться некоторыми приемами, которые я использую при построении своих роботов. См. тему -мой робот. ----------------- В структуре спинного мозга робота , реализуемого в QUIK, условно можно выделить колбеки, в которые приходят сообщения о событиях и функцию main, работающую в отдельном потоке, которая обрабатывает эти события. ------------------- Как правило, в main делается бесконечный цикл, в котором и осуществляется обработка. ------------------------ Если обрабатывать нечего, то рекомендуют ставить sleep(time) , где time - число миллисекундах, на которое Вы заморозите работу main. =============== Но проблема в том, что пока Main не работает, в колбеки поступают события , которые надо обрабатывать либо как-то сохранить иначе они потеряются. ------------------------------ Если обрабатывать события в колбеках, то на время обработки будет остановлен терминал QUIK, так как колбеки работают в основном потоке термина. ------------------ Проблема решается путем сохранения сообщений из колбеков в массиве, из которого потом main сможет обработать все сохраненные сообщения. Такой список (массив,таблица и т д) сообщений называется очередью. =================== Вопрос лишь в том, как организовать эту очередь , чтобы было просто и быстро. --------------- Есть несколько вариантов, ------------ Вариант 1: классический - это сделать универсальные списки, очереди. Алгоритмов и их реализаций много можно найти в интернете. Сложно, малопонятно не спецам . Большая нагрузка на сборщик мусора, что замедляет исполнение скрипта. -------------------- Вариант 2: -рекомендуемый в документации QLUA - использовать потоко безопасные функции записи и удаления из массива. Просто, но относительно медленно. ------------------- Вариант 3: это мой, тот который я расскажу далее. просто и быстро. Объяснять буду на рабочем примере. ------------------- Продолжение следует...
function t2s(t,s) -- преобразование таблицы в скрипт Lua
for z,v in pairs(t) do
if s~="" then s=s.."," end
if type(z)~="number" then s=s.."["..z.."]=" end
m=type(v);
if m=="table" then x=t2s(v,"")
elseif m=="number" then x=v;
else x='"'..v..'"'; end
s=s..x;
end
return "{"..s.."}";
end
Выложенный скрипт очереди Владимира - это просто ужас какой-то. Сборщик мусора сойдет с ума. Берем такой два варианта :
Код
--сравнительный тест размер массива
local M=1000000
local list=List.new()
local tnk=nkcc.new(32);
nklib.startA(); for i=1, M do push(tnk,i); end tim1=nklib.stopA();
print("замер массива после"..M.." циклов push="..#tnk)
nklib.startA(); for i=1, M do local x=pop(tnk); end tim2=nklib.stopA();
print("замер массива после"..M.." циклов pop="..#tnk)
nklib.startA(); for i=1, M do local x=get(tnk,i); end tim3=nklib.stopA();
print("замер массива после"..M.." циклов pop="..#tnk)
print(tim0,tim01,tim1,tim2,tim3,tim0/tim1,tim01/tim2,tim01/tim3);
Glukator написал: Да хрен его знает, этого прохвоссора, о чем он толкует. У вас какое-то конкретное практическое приложение намечено для этих структур?
Да уж куда еще конкретней. Попробую сформировать свою Задачу.
Эта тема лежит в начале обсуждения (пост #1) на этой веточки. Но куда то подевались программисты? Все "понты" по отлетали. Лучше месяцами "толочь воду в ступе" про OnInit и тому подобное.
Это делается организацией циклическим массивом. Например, в индикаторах используем не более 32 отсчетом . это тест в нем внешний цикл это поступающие отсчеты А внутренний цикла печатает какие отсчеты сейчас внутри массива
Код
local M=1000000
local tnk=newV(32);
for i=1, M do push(tnk,i);
local t1={}
for i=1,32 do t1[i]=get(tnk,i); end
print(table.concat(t1,","));
end
и вот результат: Массив содержит всегда последние 32 элемента . Сборщик мусора отдыхает. Могу показать как работает стек с фиксированной длинной и очередь с циклическим массивом. А ту очередь, которую Вам гуру по прогаммированию написалчто Вам раноше
VPM, В качестве информации к размышлению. --------------------- Вы наверное видели на форуме результаты теста хранения и обмена данных по принципу Memory-mapped files ------------------- Так вот , применение отображения файла на память решает все проблемы с объемом памяти. ---------------------------------------- 1) Объем памяти для хранения данных. У меня он ограничен свободным объемом диска. сейчас это 100 ГБайт. Очевидно, что если мало, то можно добавить еще диск специально для данных, например 500 Гбайт Цена вопроса 2тр. Понятно, что объем памяти для хранения данных практически любой. --------------------------------------- 2) Объем памяти для обработки данных. Очевидно, что обрабатывать 500 Гбайт каждую минуту нет надобности. У меня на компе есть сейчас свободных 2.5Гбайта . Для обработки за один заход мне достаточно 1 ГБайт. ------------------------------------ 3) Фокус в том, что на этот 1Гбайт менее, чем за 0.001 сек проецирую любой ГБайт из файла на диске. После проецирования работа с Гбайтом данных выполняется в памяти. ---------------- Более того, не надо ничего читать из файлов в другие потоки или приложения. ------------------------ Они видят эту оперативную память и просто работают с ней как обычно. ========================= Резюме, нет никаких ограничений ни в памяти для обработки , ни в памяти для хранения.
VPM, Вы рыбак? Покупали лодку на Аральском море ? ----------------- Если нет, то Ваш пример из раздела - " Остапа понесло" --------------- Всегда прикольно, когда начинают рассказывать примеры из совершенно чужой области деятельности. =========== Типа: "А вот им это очень нужно" или "в будущем это особо пригодится тем, .... " и так далее...
Serge123 написал: А если диск виртуальный, то как по сравнению с маппинг филес?
У меня нет виртуального диска. Могу предположить что разницы в скорости нет так как обмен практически через память , но в маппинге параллельно получаем файл в энергонезависимой памяти (диске). и нет надобности тратить память на виртуальный диск. Ну и безусловно, если данных сотни гигабайт или терабайты, то никакой виртуальный диск не поможет. ------------------------------ Возможно обмен через Shared Memory будет быстрее.
сделал тест обмена данными приложения Lua и Python. -------------------- для mapping falles скорость обмена 21 МБ/сек. Просто файлами 0.8 МБ/сек. Аналогично между приложениями на Lua. ------------------ Функции обмена реализованы на С.
nikolz написал: Я выкладывал тесты. Но с питоном не делал. Он медленнее luajit примерно в 30 раз.
да уже увидел тесты
сделал тест обмена данными приложения Lua и Python. -------------------- для mapping falles скорость обмена 21 МБ/сек. Просто файлами 0.8 МБ/сек. Аналогично между приложениями на Lua. ------------------ Функции обмена реализованы на С.
Продолжаю хвалится. -------------------------- Те кому рябит, смотрите только нижнюю картинку это профит - рассчитывается с начала этого года (есть за 2023 и частично за 2022) -------------- белая линия - это суммарный профит зеленая линия - это лонг синяя - это шорт штриховые линии - это текущий расчет. ================= Графики использую для обучения. Этот робот реализует NN -"обучение с учителем" ----------------- С самообучением пока лишь стою. ============== На верхнем графики автоматически строятся уровни поддержки и сопротивления и линии регрессии.
bespalex написал: перебор тоже вариант Чаще всего отключение происходит ровно в 10:00:05. Не знаю, может какое-то технологическое переключение в начале сессии, которое триггерит выкидыш. Не думаю, что если бы дело было в core.dll оно бы так выглядело.
Дело в том, что нет смысла передавать все данные из КВИК через луа в питон. ------------- Моя концепция создания робота такая: ----------- Робот условно содержит две части - я их назвал по аналогии с человеком - спинной и головной мозг. Спинной - это все колбеки в КВИКЕ и все торговые операции в КВИКЕ. Их нет смысла перегонять в питон и обратно. Это фактически автомат стандартных действий, которые не зависят никак от стратегии и тактики торговли. Эту часть я реализую в КВИКЕ на луа + си for lua. ------------------ Головной мозг - это прогнозы, управление капиталом, стратегии торговли можно и нужно реализовывать в дополнительных потоках и приложениях на любых языках, в том числе и питоне. --------------------- Вот для этого организую взаимодействие КВИКа через Луа с python, rust,julia, terra, luajit и т д ================ Сейчас обмен любыми данными делаю через mapping files. Скорость обмена просто аховая, так как это обмен через память . Нет никаких оберток. Поддерживаются все форматы. Строки передаю как хеш. Это фактически два целых числа. Объем данных ограничен лишь объемом дисков. ---------------------- Хочу сделать формирования запроса произвольных данных от сторонних приложений. =========== И еще замечу, что если Вы исполняете скрипт для питона без jit либо трансляции в СИ, то это раз в пять медленнее, чем на луа.
Спасибо, очень познавательно. У меня задача немного проще сейчас: адаптировать существующего робота для работы с Quik. В принципе не сказать, что медленно, цикл проверок на триггер занимает около 50-150мс (вместе со всеми транзакциями). Для моих задач этого пока достаточно. В вашей системе какое время обработки получается?
Добрый день, Тестил скорость питона, луа и jit и решил посмотреть, как ускорит Lua5.4 по сравнению 5.3. -------------------- Раньше было быстрее, да и интернет говорит об этом же. Выкладывал тест на форуме. ------------ Но получился прикол. На этом тесте оказалось наоборот Вот этот testSM.lua
Код
local V={}
local t=os.clock();
local N=256;
local y=0.;
local A=100.
local P=128
local W=2*3.14/N
for i=1,10000000 do
y=A*math.sin(W*i);
end
local t1=os.clock()
print("time="..t1-t,"y="..y);
nikolz написал: Да, тики - это лишь текущие торги. Свечи - на сервер КВИК формируются и хранятся 3000 шт. Если не будете удалять и переустанавливать, то будут накапливаться в арктве на компе бБлагодарю за пояснение
Благодарю за пояснение! По поводу второго вашего поста, нужно было сделать только один запрос с последующим экспортом в cvs например, но теперь я понял что нужно искать другие источники биржевых данных, либо накапливать самому во внешнюю БД
перебор тоже вариант Чаще всего отключение происходит ровно в 10:00:05. Не знаю, может какое-то технологическое переключение в начале сессии, которое триггерит выкидыш. Не думаю, что если бы дело было в core.dll оно бы так выглядело.
Дело в том, что нет смысла передавать все данные из КВИК через луа в питон. ------------- Моя концепция создания робота такая: ----------- Робот условно содержит две части - я их назвал по аналогии с человеком - спинной и головной мозг. Спинной - это все колбеки в КВИКЕ и все торговые операции в КВИКЕ. Их нет смысла перегонять в питон и обратно. Это фактически автомат стандартных действий, которые не зависят никак от стратегии и тактики торговли. Эту часть я реализую в КВИКЕ на луа + си for lua. ------------------ Головной мозг - это прогнозы, управление капиталом, стратегии торговли можно и нужно реализовывать в дополнительных потоках и приложениях на любых языках, в том числе и питоне. --------------------- Вот для этого организую взаимодействие КВИКа через Луа с python, rust,julia, terra, luajit и т д ================ Сейчас обмен любыми данными делаю через mapping files. Скорость обмена просто аховая, так как это обмен через память . Нет никаких оберток. Поддерживаются все форматы. Строки передаю как хеш. Это фактически два целых числа. Объем данных ограничен лишь объемом дисков. ---------------------- Хочу сделать формирования запроса произвольных данных от сторонних приложений. =========== И еще замечу, что если Вы исполняете скрипт для питона без jit либо трансляции в СИ, то это раз в пять медленнее, чем на луа.
Робеспьер написал: Всем привет! Полагаю, что получение тиковых данных возможно только при активной биржевой сессии, или возможно, это от брокера зависит.
Код
class_code = "TQBR"
sec_code = "GAZP"
function main ()
ds, err = CreateDataSource (class_code, sec_code, INTERVAL_TICK)
if err ~ = nil then
message (tostring( message ))
return
end
err = ds: SetEmptyCallback ()
if err = = false then
message (tostring(err))
return
end
message ( string.format ( "Sizeof: %s" , ds: Size ()))
end
Вывод: Sizeof: 0 При использовании свечного интервала, к примеру: INTERVAL_M1, наоборот данные возвращаются со всеми полями OHLC
у вас неправильно написана программа. Надо один раз подписываться на источник, а не долбить сервер заявками на подписку. Сами тики приходят в колбек onAllTrade ---------------- На форуме я выкладывал скрипт с очередью данных из колбеков и подпиской. посмотрите и повторите.
Да, тики - это лишь текущие торги. Свечи - на сервер КВИК формируются и хранятся 3000 шт. Если не будете удалять и переустанавливать, то будут накапливаться в арктве на компе больше.