--[[ nick-h@yandex.ru https://github.com/nick-nh/qlua Горизонтальные объемы. Профиль. ]] local logFile = nil -- logFile = io.open(_G.getWorkingFolder().."\\LuaIndicators\\priceAvgProfile.log", "w") _G.unpack = rawget(table, "unpack") or _G.unpack _G.Settings = {} _G.Settings.Name = "*priceAvgProfile" _G.Settings.period = 180 -- Число бар истории для анализа _G.Settings.shift = 100 -- Сдвиг линий по горизонтали влево _G.Settings.barShift = 0 -- Сдиг бар для анализа от последнего _G.Settings.weeks = 0 -- 1 - текущая, отрицательное число - сколько прошлых недель, включая текущую _G.Settings.fixShift = 1 -- 1 - всегда смещено на указанное количество shift, если 0, то будет смещено на дату начала недели расчета _G.Settings.bars_in_line = 50 -- Максимальная длина линий в барах. Не должна превышать период построения. _G.Settings.showMaxLine = 1 _G.Settings.partMode = 0 -- Режим формирования отдельных данных для каждого интервала. В этом режиме данные будут формироваться каждые partBars _G.Settings.partBars = 60 -- Число бар интервала для формирования данных _G.Settings.partPeriod = 0 --[[Интервал привязки данных в минутах. 60 - будут привязаны к началу часа. При этом ТФ построения должен быть меньше. Для примера строим на ТФ М1, каждые 60 бар, с привязкой к ТФ 60 мин. 0 - выключено. Произвольная привязка от последнего бара при запуске.]] --------------------------------------------------------------------------------------- local lines = 100 local scale = 2 local min_price_step = 1 local error_log = {} local math_max = math.max local math_min = math.min local math_floor = math.floor local math_ceil = math.ceil local math_pow = function(x, y) return x^y end local os_time = os.time local os_date = os.date local PlotLines = function() end local O = _G['O'] local H = _G['H'] local L = _G['L'] local V = _G['V'] local T = _G['T'] local Size = _G['Size'] local SetRangeValue = _G['SetRangeValue'] local CandleExist = _G['CandleExist'] local message = _G['message'] local function log_tostring(...) local n = select('#', ...) if n == 1 then return tostring(select(1, ...)) end local t = {} for i = 1, n do t[#t + 1] = tostring((select(i, ...))) end return table.concat(t, " ") end local function myLog(...) if logFile==nil then return end logFile:write(tostring(os_date("%c",os_time())).." "..log_tostring(...).."\n"); logFile:flush(); end --------------------------------------------------------------------------------------- local function Algo(Fsettings) local period = Fsettings.period or 180 local shift = Fsettings.shift or 100 local barShift = Fsettings.barShift or 0 local weeks = Fsettings.weeks or 0 local fixShift = Fsettings.fixShift or 0 local showMaxLine = Fsettings.showMaxLine or 0 local partMode = Fsettings.partMode or 0 local partBars = Fsettings.partBars or 60 local partPeriod = Fsettings.partPeriod or 60 local bars_in_line = Fsettings.bars_in_line or 50 local part_shift = 0 shift = partMode == 1 and partBars or math_max(bars_in_line+1, shift) weeks = partMode == 1 and 0 or weeks bars_in_line = partMode == 1 and math_min(bars_in_line, partBars) or bars_in_line local cacheL = {} local cacheH = {} local weeksBegin = {} local maxPriceLine = {} local beginIndex = 0 local beginTime = 0 error_log = {} local outlines = {} local calculated_buffer={} local ds_info local ds_shift = 0 local bars = 0 local interval = 1 local function get_begin_time(sdt) local bar_time = os_time(sdt) local p_bar_time = bar_time sdt.sec = 0 if partPeriod > 1 and partPeriod <= 60 then sdt.min = math_floor(sdt.min/partPeriod)*partPeriod p_bar_time = os_time(sdt) end if partPeriod > 60 then sdt.hour = 0; sdt.min = 0 local day_begin_time = os_time(sdt) p_bar_time = day_begin_time + math_floor((bar_time - day_begin_time)/part_shift)*part_shift - ds_shift end -- return math_floor((bar_time - p_bar_time)/60/interval) return p_bar_time, math_floor((bar_time - p_bar_time)/ds_shift) end return function(index) local status, res = pcall(function() if ds_info == nil or index == 1 then ds_info = _G.getDataSourceInfo() interval = ds_info.interval if ds_info.interval == -1 then interval = 1440 end if ds_info.interval == -2 then interval = 10080 end if ds_info.interval == -3 then interval = 23200 end ds_shift = interval*60 maxPriceLine = {} weeksBegin = {} cacheL = {} cacheL[index] = L(index) or 0 cacheH = {} cacheH[index] = H(index) or 0 calculated_buffer = {} outlines = {} beginIndex = math_max(Size() - barShift, 1) beginTime = os.time(T(beginIndex)) part_shift = ds_shift*partBars if partMode == 1 then beginIndex = math_max(Size() - period, 1) -- 40 - 20 = 20 {10 - 19} 10 бар las bar not count beginTime = os.time(T(beginIndex)) -- 08:00 -- myLog('init beginIndex', beginIndex, os_date('%Y.%m.%d %H:%M', beginTime), 'interval', interval, 'part_shift', part_shift) if partPeriod ~= 0 then part_shift = partPeriod*60 local begin_time, begin_shift = get_begin_time(T(beginIndex)) beginTime = begin_time beginIndex = beginIndex - begin_shift end beginTime = beginTime + part_shift -- beginTime = beginTime - ds_shift -- 07:59 end -- myLog('index '..tostring(index), os_date('%Y.%m.%d %H:%M', os.time(T(index))), 'beginIndex', beginIndex, os_date('%Y.%m.%d %H:%M', os.time(T(beginIndex))), 'beginTime', os_date('%Y.%m.%d %H:%M', beginTime)) return nil end cacheL[index] = cacheL[index-1] cacheH[index] = cacheH[index-1] if not CandleExist(index) then return maxPriceLine[index] end local bar_time = os_time(T(index)) cacheH[index] = H(index) cacheL[index] = L(index) if T(index).week_day<T(index-1).week_day or T(index).year>T(index-1).year then weeksBegin[#weeksBegin+1] = index end if calculated_buffer[index] ~= nil then return maxPriceLine[index] end if partMode and index >= beginIndex then bars = bars + 1 end if (bar_time < beginTime or index < beginIndex or (partMode == 1 and bars < partBars and interval >= 60)) and index ~= Size() then return nil end if partMode == 0 then beginIndex = index - period if weeks == 1 then beginIndex = weeksBegin[#weeksBegin] or beginIndex end if weeks < 0 then beginIndex = weeksBegin[#weeksBegin+weeks] or beginIndex end if fixShift == 0 then shift = math_max(bars_in_line+1, index - beginIndex) end end local lines_begin = index - shift local delta_shift = 1 if partMode == 1 then lines_begin = beginIndex delta_shift = 0 end lines_begin = math_max(lines_begin, 1) if showMaxLine==1 then SetRangeValue(1, lines_begin - delta_shift, index-1, nil) end for i=1,#outlines do SetRangeValue(i+1, lines_begin - delta_shift, index-1, nil) outlines[i].index = lines_begin outlines[i].val = nil end -- myLog('index '..tostring(index), os_date('%Y.%m.%d %H:%M', bar_time), 'bars', bars, 'lines_begin', lines_begin, os_date('%Y.%m.%d %H:%M', os.time(T(lines_begin))), 'beginIndex', beginIndex, 'beginTime', os_date('%Y.%m.%d %H:%M', beginTime), 'beginIndex Time', CandleExist(beginIndex) and os_date('%Y.%m.%d %H:%M', os.time(T(beginIndex)))) -- myLog('weeks '..tostring(weeks)..' last '..tostring(weeksBegin[#weeksBegin])..' beginIndex '..tostring(beginIndex)) local maxPrice = math_max(unpack(cacheH, lines_begin, index-1)) local minPrice = math_min(unpack(cacheL, lines_begin, index-1)) ---------------------------------------- local priceProfile = {} local clasterStep = math_max((maxPrice - minPrice)/lines, min_price_step) -- myLog('minPrice '..tostring(minPrice)..' maxPrice '..tostring(maxPrice)..' clasterStep '..tostring(clasterStep)) for i = 0, (index-1-lines_begin) do if CandleExist(index-i) then local barSteps = math_max(math_ceil((H(index-i) - L(index-i))/clasterStep),1) for j=0,barSteps-1 do local clasterPrice = math_floor((L(index-i) + j*clasterStep)/clasterStep)*clasterStep local clasterIndex = clasterPrice*math_pow(10, scale) if priceProfile[clasterIndex] == nil then priceProfile[clasterIndex] = {price = clasterPrice, vol = 0} end priceProfile[clasterIndex].vol = priceProfile[clasterIndex].vol + V(index-i)/barSteps -- myLog('index', index-i, 'clasterIndex '..tostring(clasterIndex)..' vol '..tostring(priceProfile[clasterIndex].vol)) end end end -------------------- local MAXV = 0 local maxVolPrice = 0 local maxCount = 0 local sortedProfile = {} for _, profileItem in pairs(priceProfile) do MAXV=math_max(MAXV,profileItem.vol) if MAXV == profileItem.vol then maxVolPrice=profileItem.price end maxCount = maxCount + 1 sortedProfile[maxCount] = {price = profileItem.price, vol = profileItem.vol} end -- myLog('maxV '..tostring(MAXV)..' tblMax '..tostring(sortedProfile[1].vol)) if maxVolPrice == 0 then maxVolPrice = O(index-1) end table.sort(sortedProfile, function(a,b) return (a['vol'] or 0) > (b['vol'] or 0) end) --------------------- for i=1,lines do outlines[i] = {index = lines_begin + bars_in_line - 1, val = nil} if sortedProfile[i]~=nil and sortedProfile[i].price ~= maxVolPrice then sortedProfile[i].vol = math_floor(sortedProfile[i].vol/MAXV*bars_in_line) if sortedProfile[i].vol>0 then outlines[i].index = lines_begin + sortedProfile[i].vol - 1 outlines[i].val = sortedProfile[i].price end end SetRangeValue(i+1, lines_begin, outlines[i].index, outlines[i].val) --myLog('line '..tostring(i).." price "..tostring(GetValue(lines_begin, i)).." - "..tostring(GetValue(outlines[i].index, i)).." vol "..tostring(outlines[i].index-index+shift)) end if showMaxLine==1 then SetRangeValue(1, lines_begin, index-1, maxVolPrice) maxPriceLine[index] = maxVolPrice end calculated_buffer[index] = true if partMode == 1 then if bar_time >= beginTime and (bars >= partBars or interval < 60) then -- bar_time 08:00 > 07:59 beginIndex = index beginTime = beginTime + part_shift-- 08:59 maxPriceLine = {} bars = 1 -- myLog('-- index '..tostring(index), os_date('%Y.%m.%d %H:%M', bar_time), 'beginIndex', beginIndex, 'new begin time', os_date('%Y.%m.%d %H:%M', beginTime)) -- return end end end) if not status then if not error_log[tostring(res)] then error_log[tostring(res)] = true myLog(tostring(res)) message(tostring(res)) end end -- return maxPriceLine[index] end end function _G.Init() _G.Settings.line = {} _G.Settings.line[1] = {} _G.Settings.line[1] = {Name = 'maxVol', Color = _G.RGB(255, 128, 64), Type = _G.TYPET_BAR, Width = 2} for i = 1, lines do _G.Settings.line[i+1] = {} _G.Settings.line[i+1] = {Color = _G.RGB(185, 185, 185), Type = _G.TYPET_BAR, Width = 2} end PlotLines = Algo(_G.Settings) return lines end function _G.OnChangeSettings() _G.Init() end function _G.OnCalculate(index) if index == 1 then local DSInfo = _G.getDataSourceInfo() min_price_step = tonumber(_G.getParamEx(DSInfo.class_code, DSInfo.sec_code, "SEC_PRICE_STEP").param_value) or 0 scale = tonumber(_G.getSecurityInfo(DSInfo.class_code, DSInfo.sec_code).scale) or 0 end return PlotLines(index) end