本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © LeviathanCapital
//@version=5
indicator("Volume / Open Interest Footprint - By Leviathan",shorttitle = 'Volume / OI Footprint - By Leviathan', overlay=true, max_boxes_count=500, max_bars_back = 1000)
t1 = 'Choose the data that will be analyzed and displayed in the bar'
t2 = 'Resolution is the number of rows displayed in a bar. Increasing it will provide more granular data and vice versa. You might need to decrease the resolution when viewing larger ranges'
t3 = 'Total (Total Volume or Total Open Interest Increase), Up/Down (Buy/Sell Volume or OI Increase/Decrease), Delta (Buy Volume - Sell Volume or OI Increase - OI Decrease)'
t4 = 'This input moves the bar to the right for N number of bars'
t5 = 'This input defines the width of the footprint bar (in number of bars)'
t6 = 'Choose location where the bar is positioned (relative to the visible range)'
t7 = 'This will aggregate volume from your selected sources. Make sure to turn on RQC if volume is reported in quote currrency (eg. USD)'
t8 = 'This will draw price levels where Volume/OI Delta was positive. These may serve as significant points of interest'
voltype = input.string('Volume', 'Source', options=['Volume', 'Open Interest'], tooltip = t1)
res = input.int(20, 'Resolution', minval=5, tooltip = t2)
ntype = input.string('Up/Down', 'Type', options = ['Total', 'Up/Down', 'Delta'], tooltip = t3)
pos = input.string('Right', 'Positioning', options = ['Right', 'Left'], tooltip = t6)
st1 = input.int(30, 'Offset', tooltip = t4)
wid = input.int(40, 'Width', tooltip = t5)
sr = input.bool(false, 'Positive Delta Levels')
// Aggregate
aggr = input.bool(false, 'Aggregate Volume', group = 'Volume Aggregation')
A0 = input.bool(true, ' CHART PAIR', group = 'Volume Aggregation')
A1B = input.bool(false, '' , inline='a1', group = 'Volume Aggregation')
A2B = input.bool(false, '' , inline='a2', group = 'Volume Aggregation')
A3B = input.bool(false, '' , inline='a3', group = 'Volume Aggregation')
A4B = input.bool(false, '' , inline='a4', group = 'Volume Aggregation')
A5B = input.bool(false, '' , inline='a5', group = 'Volume Aggregation')
A6B = input.bool(false, '' , inline='a6', group = 'Volume Aggregation')
A7B = input.bool(false, '' , inline='a7', group = 'Volume Aggregation')
A8B = input.bool(false, '' , inline='a8', group = 'Volume Aggregation')
A1 = input.symbol('BINANCE:BTCUSDTPERP', '' , inline='a1', group = 'Volume Aggregation')
A1t = input.bool(false, 'RQC', inline='a1', group = 'Volume Aggregation')
A2 = input.symbol('BYBIT:BTCUSDT.P', '' , inline='a2', group = 'Volume Aggregation')
A2t = input.bool(false, 'RQC', inline='a2', group = 'Volume Aggregation')
A3 = input.symbol('OKX:BTCUSDT.P', '' , inline='a3', group = 'Volume Aggregation')
A3t = input.bool(false, 'RQC', inline='a3', group = 'Volume Aggregation')
A4 = input.symbol('BINGX:BTCUSDT', '' , inline='a4', group = 'Volume Aggregation')
A4t = input.bool(false, 'RQC', inline='a4', group = 'Volume Aggregation')
A5 = input.symbol('BITGET:BTCUSDT.P', '' , inline='a5', group = 'Volume Aggregation')
A5t = input.bool(false, 'RQC', inline='a5', group = 'Volume Aggregation')
A6 = input.symbol('PHEMEX:BTCUSDT.P', '' , inline='a6', group = 'Volume Aggregation')
A6t = input.bool(false, 'RQC', inline='a6', group = 'Volume Aggregation')
A7 = input.symbol('MEXC:BTCUSDT.P', '' , inline='a7', group = 'Volume Aggregation')
A7t = input.bool(false, 'RQC', inline='a7', group = 'Volume Aggregation')
A8 = input.symbol('WOONETWORK:BTCUSDT.P', '' , inline='a8', group = 'Volume Aggregation')
A8t = input.bool(false, 'RQC', inline='a8', group = 'Volume Aggregation')
A1vol = A1B ? request.security(A1, timeframe.period, A1t ? volume/close : volume) : 0
A2vol = A2B ? request.security(A2, timeframe.period, A2t ? volume/close : volume) : 0
A3vol = A3B ? request.security(A3, timeframe.period, A3t ? volume/close : volume) : 0
A4vol = A4B ? request.security(A4, timeframe.period, A4t ? volume/close : volume) : 0
A5vol = A5B ? request.security(A5, timeframe.period, A5t ? volume/close : volume) : 0
A6vol = A6B ? request.security(A6, timeframe.period, A6t ? volume/close : volume) : 0
A7vol = A7B ? request.security(A7, timeframe.period, A7t ? volume/close : volume) : 0
A8vol = A8B ? request.security(A8, timeframe.period, A8t ? volume/close : volume) : 0
//Appearance
ndsize = input.string('Small', 'Node Text Size', options = ['Tiny', 'Small', 'Normal', 'Large', 'Auto'], group= 'Additional Settings')
ndpos = input.string('Center', 'Node Text Position', options = ['Left', 'Right', 'Center'], group= 'Additional Settings')
netcol1 = input.color(color.rgb(255, 82, 82, 100), 'Negative Cold/Hot', inline='n', group= 'Additional Settings')
netcol2 = input.color(#f7525f, '', inline='n', group= 'Additional Settings')
netcol3 = input.color(color.rgb(76, 175, 79, 100), 'Positive Cold/Hot ', inline='p', group= 'Additional Settings')
netcol4 = input.color(#81c784, '', inline='p', group= 'Additional Settings')
totcol1 = input.color(color.rgb(33, 149, 243, 100), 'Total Cold/Hot ', inline='t', group= 'Additional Settings')
totcol2 = input.color(color.blue,'', inline='t', group= 'Additional Settings')
pdlcol = input.color(color.rgb(33, 149, 243, 74),'Positive Delta Color', group= 'Additional Settings')
hmopt = input.bool(true, 'Total Based Coloring', group= 'Additional Settings')
//
st = chart.right_visible_bar_time == time or pos=='Left' ? st1 : -2 * wid
float data = aggr ? (A0 ? volume : 0) + A1vol + A2vol + A3vol + A4vol + A5vol + A6vol + A7vol + A8vol : volume
//
startTime = chart.left_visible_bar_time
endTime = chart.right_visible_bar_time
//
bool inZone = time >= startTime and time <= endTime
bool newSession = inZone and not inZone[1]
bool endSession = not inZone and inZone[1]
var int barsInSession = 0
barsInSession := inZone ? barsInSession + 1 : barsInSession
var zoneStartIndex = 0
var zoneEndIndex = 0
var int zoneStart = 0
if newSession
zoneStartIndex := bar_index
if endSession
zoneEndIndex := bar_index
int lookback = bar_index - zoneStart
var activeZone = false
//
profHigh = ta.highest(high, barsInSession+1)[1]
profLow = ta.lowest(low, barsInSession+1)[1]
resolution = res
//
var vpGreen = array.new_float(resolution, 0)
var vpRed = array.new_float(resolution, 0)
var vpDelta = array.new_float(resolution, 0)
var vpTotal = array.new_float(resolution, 0)
var zoneBounds = array.new_float(resolution, 0)
//
var float[] ltfOpen = array.new_float(0)
var float[] ltfClose = array.new_float(0)
var float[] ltfHigh = array.new_float(0)
var float[] ltfLow = array.new_float(0)
var float[] ltfVolume = array.new_float(0)
//
string userSymbol = 'BINANCE' + ":" + string(syminfo.basecurrency) + 'USDT.P'
string openInterestTicker = str.format("{0}_OI", userSymbol)
string timeframe = syminfo.type == "futures" and timeframe.isintraday ? "1D" : timeframe.period
deltaOi = request.security(openInterestTicker, timeframe, close-close[1], ignore_invalid_symbol = true)
oi = request.security(openInterestTicker, timeframe.period, close, ignore_invalid_symbol = true)
pldeltaoi = deltaOi
//
vol() =>
out = data
if voltype == 'Open Interest'
out := deltaOi
out
//
float dO = open
float dC = close
float dH = high
float dL = low
float dV = vol()
//
switchLineStyle(x) =>
switch x
'────' => line.style_solid
'--------' => line.style_dashed
'┈┈┈┈' => line.style_dotted
switchPos(x) =>
switch x
'Left' => text.align_left
'Right' => text.align_right
'Center' => text.align_center
switchPlotStyle(x) =>
switch x
'•••••••••' => plot.style_circles
'────' => plot.style_linebr
switchsize(x) =>
switch x
'Tiny' => size.tiny
'Small' => size.small
'Normal' => size.normal
'Large' => size.large
'Auto' => size.auto
//
resetProfile(enable) =>
if enable
array.fill(vpGreen, 0)
array.fill(vpRed, 0)
array.clear(ltfOpen)
array.clear(ltfHigh)
array.clear(ltfLow)
array.clear(ltfClose)
array.clear(ltfVolume)
//
tr = ta.atr(1)
atr = ta.atr(14)
//
get_vol(y11, y12, y21, y22, height, vol) =>
nz(math.max(math.min(math.max(y11, y12), math.max(y21, y22)) - math.max(math.min(y11, y12), math.min(y21, y22)), 0) * vol / height)
//
profileAdd(o, h, l, c, v, g, w) =>
for i = 0 to array.size(vpGreen) - 1
zoneTop = array.get(zoneBounds, i)
zoneBot = zoneTop - g
body_top = math.max(c, o)
body_bot = math.min(c, o)
itsgreen = c >= o
topwick = h - body_top
bottomwick = body_bot - l
body = body_top - body_bot
bodyvol = body * v / (2 * topwick + 2 * bottomwick + body)
topwickvol = 2 * topwick * v / (2 * topwick + 2 * bottomwick + body)
bottomwickvol = 2 * bottomwick * v / (2 * topwick + 2 * bottomwick + body)
if voltype == 'Volume'
array.set(vpGreen, i, array.get(vpGreen, i) + (itsgreen ? get_vol(zoneBot, zoneTop, body_bot, body_top, body, bodyvol) : 0) + get_vol(zoneBot, zoneTop, body_top, h, topwick, topwickvol) / 2 + get_vol(zoneBot, zoneTop, body_bot, l, bottomwick, bottomwickvol) / 2)
array.set(vpRed, i, array.get(vpRed, i) + (itsgreen ? 0 : get_vol(zoneBot, zoneTop, body_bot, body_top, body, bodyvol)) + get_vol(zoneBot, zoneTop, body_top, h, topwick, topwickvol) / 2 + get_vol(zoneBot, zoneTop, body_bot, l, bottomwick, bottomwickvol) / 2)
array.set(vpDelta, i, array.get(vpGreen, i) - array.get(vpRed, i))
array.set(vpTotal, i, array.get(vpGreen, i) + array.get(vpRed, i))
else if voltype == 'Open Interest'
if v > 0
array.set(vpGreen, i, array.get(vpGreen, i) + get_vol(zoneBot, zoneTop, body_bot, body_top, body, v))// + get_vol(zoneBot, zoneTop, body_top, h, topwick, topwickvol) / 2 + get_vol(zoneBot, zoneTop, body_bot, l, bottomwick, bottomwickvol) / 2)
array.set(vpDelta, i, array.get(vpGreen, i) - array.get(vpRed, i))
array.set(vpTotal, i, array.get(vpGreen, i))
if v < 0
array.set(vpRed, i, array.get(vpRed, i) + get_vol(zoneBot, zoneTop, body_bot, body_top, body, -v))// + get_vol(zoneBot, zoneTop, body_top, h, topwick, topwickvol) / 2 + get_vol(zoneBot, zoneTop, body_bot, l, bottomwick, bottomwickvol) / 2)
//
calcSession(update) =>
array.fill(vpGreen, 0)
array.fill(vpRed, 0)
array.fill(vpDelta, 0)
if bar_index > lookback and update
gap = (profHigh - profLow) / resolution
for i = 0 to resolution - 1
array.set(zoneBounds, i, profHigh - gap * i)
if array.size(ltfOpen) > 0
for j = 0 to array.size(ltfOpen) - 1
profileAdd(array.get(ltfOpen, j), array.get(ltfHigh, j), array.get(ltfLow, j), array.get(ltfClose, j), array.get(ltfVolume, j), gap, 1)
//
f_newNode(condition, x, top, right, bott, col, txt) =>
condition ? box.new(x, top, right, bott, bgcolor=col, border_width=0, text=txt,xloc=xloc.bar_index, text_size = switchsize(ndsize), text_color = chart.fg_color, text_halign = switchPos(ndpos)) : na
//
drawNewZone(update) =>
var box[] profileBoxesArray = array.new_box(0)
float leftMax = zoneStartIndex
float rightMax = (((barstate.islast and inZone) ? bar_index : zoneEndIndex) - zoneStartIndex) + zoneStartIndex
if update and array.sum(vpGreen) + array.sum(vpRed) > 0
gap = (profHigh - profLow) / resolution
float rightMaxVol = array.max(vpGreen)+array.max(vpRed)
float buffer = 0
size = array.size(profileBoxesArray)
if size > 0
for j = 0 to size - 1
box.delete(array.get(profileBoxesArray, size - 1 - j))
array.remove(profileBoxesArray, size - 1 - j)
for i = 0 to array.size(vpRed) - 1
vrpct = array.percentrank(vpGreen, i)/100
vgpct = array.percentrank(vpRed, i)/100
dpct = array.percentrank(vpDelta, i)/100
tpct = array.percentrank(vpTotal, i)/100
deltapct = (array.percentrank(vpGreen, i) - array.percentrank(vpRed, i))/100
rcol = color.from_gradient(hmopt ? vrpct : tpct, 0, 1, netcol1, netcol2)
gcol = color.from_gradient(hmopt ? vgpct : tpct, 0, 1, netcol3, netcol4)
dgcol = color.from_gradient(dpct, 0, 1, netcol3, netcol4)
drcol = color.from_gradient(dpct, 0.1, 1, netcol2, netcol1)
upvol = math.round(array.get(vpGreen, i), 0)
downvol = math.round(array.get(vpRed, i), 0)
delta = upvol-downvol
totvol = str.tostring(voltype=='Volume' ? (upvol + downvol) : upvol , format.volume)
totvolcol = color.from_gradient(vrpct, 0, 1, totcol1, totcol2)
deltavol = str.tostring(upvol - downvol , format.volume)
gvol = str.tostring(upvol, format.volume)
rvol = str.tostring(downvol, format.volume)
left1 = pos=='Right' ? bar_index+st : int(leftMax)+st
left2 = pos=='Right' ? bar_index+(st + wid) : int(leftMax)+(st + wid)
left3 = pos=='Right' ? bar_index+st : int(leftMax)+st
right1 = pos=='Right' ? bar_index+(st + wid) : int(leftMax)+(st + wid)
right2 = pos=='Right' ? bar_index+(st + wid*2) : int(leftMax)+(st + wid*2)
right3 = pos=='Right' ? bar_index+(st + wid) : int(leftMax)+(st + wid)
if ntype=='Total'
array.push(profileBoxesArray, f_newNode(true, left3, array.get(zoneBounds, i) - buffer, right3, array.get(zoneBounds, i) - gap + buffer,totvolcol, totvol))
array.push(profileBoxesArray, f_newNode(delta>0 and sr, pos=='Right' ? int(leftMax)+st : right3 , array.get(zoneBounds, i) - buffer, bar_index+st, array.get(zoneBounds, i) - gap + buffer,pdlcol, txt=''))
if ntype=='Up/Down'
array.push(profileBoxesArray, f_newNode(true, left1, array.get(zoneBounds, i) - buffer, right1, array.get(zoneBounds, i) - gap + buffer, rcol, rvol))
array.push(profileBoxesArray, f_newNode(true, left2, array.get(zoneBounds, i) - buffer, right2, array.get(zoneBounds, i) - gap + buffer, gcol, gvol))
array.push(profileBoxesArray, f_newNode(delta>0 and sr, pos=='Right' ? int(leftMax)+st : right2 , array.get(zoneBounds, i) - buffer, bar_index+st, array.get(zoneBounds, i) - gap + buffer,pdlcol, txt=''))
if ntype=='Delta'
array.push(profileBoxesArray, f_newNode(true, left3, array.get(zoneBounds, i) - buffer, right3, array.get(zoneBounds, i) - gap + buffer,delta>0 ? dgcol : drcol, deltavol))
array.push(profileBoxesArray, f_newNode(delta>0 and sr, pos=='Right' ? int(leftMax)+st : right3 , array.get(zoneBounds, i) - buffer, bar_index+st, array.get(zoneBounds, i) - gap + buffer,pdlcol, txt=''))
//
combArray(arr1, arr2) =>
out = array.copy(arr1)
if array.size(arr2) > 0
for i = 0 to array.size(arr2) - 1
array.push(out, array.get(arr2, i))
out
//
updateIntra(o, h, l, c, v) =>
array.push(ltfOpen, o)
array.push(ltfHigh, h)
array.push(ltfLow, l)
array.push(ltfClose, c)
array.push(ltfVolume, v)
//
calcSession(endSession or (barstate.islast and inZone))
drawNewZone(endSession or (barstate.islast and inZone))
resetProfile(newSession)
//
if inZone
updateIntra(dO, dH, dL, dC, dV)
if endSession
activeZone := false
if newSession
zoneStart := bar_index
activeZone := true