forked from nick-nh/qlua
-
Notifications
You must be signed in to change notification settings - Fork 0
/
priceAvgProfile.lua
367 lines (293 loc) · 12.3 KB
/
priceAvgProfile.lua
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
--[[
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