-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathVP
More file actions
215 lines (193 loc) · 13.2 KB
/
VP
File metadata and controls
215 lines (193 loc) · 13.2 KB
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
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// https://www.tradingview.com/script/WWqoUu7e-Volume-Market-Profile/
// © SamRecio
//@version=5
indicator("Volume Profile", shorttitle = "VP", overlay = true, max_lines_count = 500, max_boxes_count = 500)
//Profile Settings
tf = input.timeframe("W", title = "Timeframe", inline = "0", group = "PROFILE SETTINGS")
vap = input.float(70, title = "Value Area %", inline = "1_1", group = "PROFILE SETTINGS")/100
//Display Settings
disp_size = input.int(-100, minval = -500,maxval = 500,title = "Display Size ", inline = "3", group = "DISPLAY SETTINGS", tooltip = "The entire range of your profile will scale to fit inside this range.\nNotes:\n-This value is # bars away from your profile's axis.\n-The larger this value is, the more granular your (horizontal) view will be. This does not change the Profiles' value; because of this, sometimes the POC looks tied with other values widely different. The POC CAN be tied to values close to it, but if the value is far away it is likely to just be a visual constraint.\n-This Value CAN be negative")
prof_offset = input.int(100, minval = -500,maxval = 500, title = "Display Offset", inline = "4", group = "DISPLAY SETTINGS", tooltip = "Offset your profile's axis (Left/Right) to customize your display to fit your style.\nNotes:\n-Zero = Current Bar\n-This Value CAN be negative")
//Additional Data Displays
extend_day = input.bool(false, title = "Extend POC/VAH/VAL", inline = "1", group = "Additional Data Displays")
lab_tog = input.bool(true, title = "Label POC/VAH/VAL", inline = "2", group = "Additional Data Displays")
//Colors
poc_color = input.color(color.rgb(247, 82, 95), title = "POC Color", group = "Colors")
var_color = input.color(color.rgb(255, 255, 255, 20), title = "VAH/VAL Color", group = "Colors")
vaz_color = input.color(color.rgb(149, 152, 161, 50), title = "Value Zone Color", group = "Colors")
ov_color = input.color(color.rgb(93, 96, 107), title = "Profile Color", group = "Colors")
///_________________________________________
///Misc Stuff
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
decimal_places = str.contains(str.tostring(syminfo.mintick),".")?str.length(array.get(str.split(str.tostring(syminfo.mintick),"."),1)):0
round_to(_round,_to) =>
math.round(_round/_to)*_to
///_________________________________________
///Setup for Length
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
tf_change = timeframe.change(tf)
var int bi_nd = na // BI_ND = Bar Index _ New Day
var int bi_lnd = na //BI_LND = Bar Index Last New Day
bi_lnd := tf_change?bi_nd:bi_lnd
bi_nd := tf_change?bar_index:bi_nd
tf_len = (bi_nd - bi_lnd)+1
bs_newtf = (bar_index - bi_nd)+1
id_lb_bars = timeframe.in_seconds(tf)/timeframe.in_seconds(timeframe.period)
dwm_lb_bars = (math.max(tf_len,bs_newtf)>1?math.max(tf_len,bs_newtf)-1:1)
auto_lb_bars = timeframe.in_seconds(tf) >= timeframe.in_seconds("D")?dwm_lb_bars:id_lb_bars
lb_bars = na(bs_newtf)?bar_index:bs_newtf
///_________________________________________
///Warning for Large Timeframe
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
var d = dayofmonth
var m = switch
month == 1 => "Jan"
month == 2 => "Feb"
month == 3 => "Mar"
month == 4 => "Apr"
month == 5 => "May"
month == 6 => "Jun"
month == 7 => "Jul"
month == 8 => "Aug"
month == 9 => "Sep"
month == 10 => "Oct"
month == 11 => "Nov"
month == 12 => "Dec"
var y = year - (int(year/100)*100)
send_warning = na(tf_len) or na(bs_newtf)
warning = "[WARNING ⚠]"+" ["+tf+"]"+"\nData Start: " + str.tostring(d,"00")+" "+m + " '" + str.tostring(y) + "\n\n"
if na(volume)
runtime.error("No Volume Data. Please Use a Ticker with Volume Data or Switch to Market Profile Calculation.")
///_________________________________________
///Start Data Accumulation
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
//By storing the data, I can use it later without the need for historical reference.
//Additionally, I can control the amount of data kept in memory to only hold the lookback amount of bars.
//This is all of the advantages of lookback calcs, with all the advantages of progressive calculations.
src_high = math.round_to_mintick(high)
src_low = math.round_to_mintick(low)
var highs = array.new_float(na)
var lows = array.new_float(na)
var vol = array.new_float(na)
highs.push(src_high)
lows.push(src_low)
vol.push(volume)
if highs.size() >= lb_bars
for i = highs.size()-1 to lb_bars
if highs.size() == lb_bars
break
else
highs.shift()
lows.shift()
vol.shift()
//Granularity Gets more corse farther from current bar for speed. Replay mode can get a full gran profile if needed.
v_gran = bar_index>=(last_bar_index-1000)?999: bar_index>=(last_bar_index-2000)?249: bar_index>=(last_bar_index-4000)?124:64
tick_size = math.max(syminfo.mintick,(highs.max()-lows.min())/v_gran)
///_________________________________________
///Profile Calcs
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
data_map = map.new<float,float>()
if barstate.islast
for i = 0 to array.size(highs)-1
hi = round_to(highs.get(i),tick_size)
lo = round_to(lows.get(i),tick_size)
candle_index = ((hi-lo)/tick_size)
tick_vol = math.round((vol.get(i)/(candle_index+1)),3)
for e = 0 to candle_index
val = round_to(lo+(e*tick_size),tick_size)
data_map.put(val, math.round(nz(data_map.get(val)),3)+tick_vol)
///_________________________________________
////Other Profile Values
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
keys = map.keys(data_map)
values = map.values(data_map)
array.sort(keys, order.ascending)
///_________________________________________
///AQUIRE POC
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
//POC = Largest Volume Closest to Price Average of Profile
//
float poc = 0
float poc_vol = 0
prof_avg = array.avg(keys)
for [key, value] in data_map
if (value > poc_vol) or (value == poc_vol and math.abs(key-prof_avg)<math.abs(poc-prof_avg))
poc := key
poc_vol := value
///_________________________________________
///VALUE ZONES
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
max_vol = array.sum(values)*vap
max_index = poc
vol_count = poc_vol
up_count = max_index
down_count = max_index
for i = 0 to array.size(keys)
if vol_count >= max_vol
break
upper_vol = nz(data_map.get(round_to(up_count+tick_size,tick_size))) + nz(data_map.get(round_to(up_count+(tick_size*2),tick_size)))
lower_vol = nz(data_map.get(round_to(down_count-tick_size,tick_size))) + nz(data_map.get(round_to(down_count-(tick_size*2),tick_size)))
if (((upper_vol >= lower_vol) and upper_vol > 0) or (lower_vol>0 == false)) and up_count < array.max(keys)-tick_size
vol_count += upper_vol
up_count := round_to(up_count+(tick_size*2),tick_size)
else if down_count > array.min(keys)+tick_size
vol_count += lower_vol
down_count := round_to(down_count-(tick_size*2),tick_size)
val = down_count
vah = up_count
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//END PROFILE CALCs//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Display//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
var profile = array.new_line(na)
var box_profile = array.new_box(na)
if barstate.islast and array.size(profile) > 0
for [index,line] in profile
line.delete(line)
array.clear(profile)
if barstate.islast and array.size(box_profile) > 0
for [index,box] in box_profile
box.delete(box)
array.clear(box_profile)
prof_color(_num,_key) => //Function for determining what color each profile index gets
switch
_key==poc => poc_color //If its the max Value, give it poc color
(_key==up_count or _key==down_count) => var_color //If its the value high or low, give it those colors
(_key>up_count or _key<down_count) => ov_color //If its above or below our value zone, give it the out of value color
=> vaz_color // Everything else is inside the value zone so it gets the value zone color
dash(_i) => (_i/2 - math.floor(_i/2)) > 0 //only true when the number is odd, Making it oscillate on/off, I can make use of this to evenly distribute boxes and lines throughout the display to make it cleaner.
roof = keys.max()
///_________________________________________
///Drawing the Profile
///‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
if barstate.islast and array.size(keys) > 0
for [i,get_key] in keys
scale = disp_size/array.max(values)
get_vol = data_map.get(get_key)
scaled = math.round(get_vol*scale)
too_far_back = bi_nd<bar_index-4999
p1 = extend_day ? (too_far_back?bar_index-4999:bi_nd) : (bar_index+prof_offset)
p2 = extend_day ? (disp_size<0?(bar_index+prof_offset):((bar_index+scaled)+prof_offset)) : ((bar_index+scaled)+prof_offset)
if get_key == poc
array.push(box_profile,box.new(p1,poc,p2,poc, border_color = poc_color, border_style = line.style_dotted, border_width = 1, extend = (extend_day and too_far_back?extend.left:extend.none)))
else if get_key == vah
array.push(box_profile,box.new(p1,vah,p2,vah, border_color = var_color, border_style = line.style_dotted, border_width = 1, extend = (extend_day and too_far_back?extend.left:extend.none)))
else if get_key == val
array.push(box_profile,box.new(p1,val,p2,val, border_color = var_color, border_style = line.style_dotted, border_width = 1, extend = (extend_day and too_far_back?extend.left:extend.none)))
else if dash(i) and (array.size(profile) <= 499) and (math.abs(scaled)>=1)
array.push(profile,line.new(bar_index+prof_offset,get_key,(bar_index+scaled)+prof_offset,get_key, color = prof_color(i,get_key), style = (get_key<down_count or get_key>up_count?line.style_dotted:line.style_solid),width = 1))
else if (array.size(box_profile) <= 499) and (math.abs(scaled)>=1)
array.push(box_profile,box.new(bar_index+prof_offset,get_key,(bar_index+scaled)+prof_offset,get_key, border_color = prof_color(i,get_key), border_style = (get_key<down_count or get_key>up_count?line.style_dotted:line.style_solid), border_width = 1))
//Drawing labels for the profile
lab = label.new(bar_index+prof_offset,roof, text = (send_warning?warning:"") + "VP[" + (send_warning?"MAX":tf) + "] ", style = (disp_size<0?label.style_label_lower_right:label.style_label_lower_left), color = color.rgb(0,0,0,100), textcolor = send_warning?color.rgb(255, 136, 0):chart.fg_color, textalign = (disp_size<0?text.align_left:text.align_right), text_font_family = font.family_monospace)
label.delete(lab[1])
if lab_tog
poc_lab = label.new(bar_index+prof_offset,poc, text = "POC", tooltip = "POC: " + str.tostring(math.round(poc,decimal_places),"$#.################"),size = size.small, style = (disp_size<0?label.style_label_left:label.style_label_right), color = color.rgb(0,0,0,100), textcolor = poc_color, textalign = (disp_size<0?text.align_right:text.align_left), text_font_family = font.family_monospace)
vah_lab = label.new(bar_index+prof_offset,vah, text = "VAH",size = size.small,tooltip = "VAH: " + str.tostring(math.round(vah,decimal_places),"$#.################"), style = (disp_size<0?label.style_label_left:label.style_label_right), color = color.rgb(0,0,0,100), textcolor = var_color, textalign = (disp_size<0?text.align_right:text.align_left), text_font_family = font.family_monospace)
val_lab = label.new(bar_index+prof_offset,val, text = "VAL",size = size.small,tooltip = "VAL: " + str.tostring(math.round(val,decimal_places),"$#.################"), style = (disp_size<0?label.style_label_left:label.style_label_right), color = color.rgb(0,0,0,100), textcolor = var_color, textalign = (disp_size<0?text.align_right:text.align_left), text_font_family = font.family_monospace)
label.delete(poc_lab[1])
label.delete(vah_lab[1])
label.delete(val_lab[1])