本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © AlgoAlpha
//@version=6
//────────────────────────────────────────────────────────────────────── Inputs
indicator("Fair Value Gap Profiles [AlgoAlpha]",
shorttitle = "AlgoAlpha – FVG Profiles",
overlay = true,
max_boxes_count = 5000)
//────────────────────────────────────────────────────────────────────── FVG Detection Inputs
volaLen = input.int(defval=50, title="Volatility Length", minval=1, tooltip="Length of ATR used to calculate fair value gap threshold", group="FVG Detection")
volaMult = input.float(defval=1.5, title="Minimum Gap Width Multiplier", minval=0.1, step=0.1, tooltip="Minimum gap width multiplier in ATR units, smaller values will cause the indicator to detect smaller gaps", group="FVG Detection")
maxBoxAge = input.int(defval=1000, title="Zone Expiration Date", tooltip="Upper limit (in bars) for how long an unfinished gap will keep stretching to the right before being deleted", group="FVG Detection")
preventOverlap = input.bool(defval=true, title="Prevent Overlap", tooltip="When enabled, gaps whose price range overlaps an existing unfinished gap are discarded", group="FVG Detection")
showBull = input.bool(defval=true, title="Show Bullish FVGs", tooltip="Enable detection and rendering of bullish (price-up) fair-value gaps", group="FVG Detection")
showBear = input.bool(defval=true, title="Show Bearish FVGs", tooltip="Enable detection and rendering of bearish (price-down) fair-value gaps", group="FVG Detection")
ltf = input.timeframe(defval="1", title="Lower Timeframe", tooltip="Timeframe from which secondary price/volume series are sampled to build the intra-gap profile", group="Profile")
bincount = input.int(defval=7, title="Profile Bin Count", minval=1, tooltip="How many horizontal slices the volume profile is divided into when analysing each gap", group="Profile")
lvWindow = input.int(defval=2, title="Low-Volume Window Size", minval=1, tooltip="Number of consecutive profile bins evaluated together when searching for the lowest-volume zone inside the gap", group="Profile")
profileScale = 0.5
bullCol = input.color(defval=#00ffbb, title="Bullish box colour", tooltip="Stroke/fill colour applied to bullish gap elements", group="Appearance")
bearCol = input.color(defval=#ff1100, title="Bearish box colour", tooltip="Stroke/fill colour applied to bearish gap elements", group="Appearance")
showProfile = input.bool(defval=true, title="Show Volume Profile", tooltip="Draw the mini volume-profile built from lower-timeframe data inside each gap", group="Appearance")
showLowVolZone = input.bool(defval=true, title="Show Low Volume Zone", tooltip="Highlight the sub-range with the smallest aggregated volume as determined by the sliding window", group="Appearance")
showGapZone = input.bool(defval=true, title="Show Main Gap Zone", tooltip="Render the main coloured rectangle that represents the full price gap", group="Appearance")
profgrad = input.bool(defval=false, title="Profile Gradient Coloring", tooltip="Enable/Disable color gradients on the profile", group="Appearance")
needVolProfile = showProfile or showLowVolZone
//────────────────────────────────────────────────────────────────────── FVG Detection Logic
var gapdist = array.new_float()
basicbullfvg = low > high[2]
basicbearfvg = high < low[2]
if basicbullfvg or basicbearfvg
gapdist.unshift(basicbullfvg ? (low - high[2]) : (low[2] - high))
while gapdist.size() > volaLen
gapdist.pop()
vola = gapdist.avg()
bullFVG = showBull and low > high[2] and (low - high[2]) > vola * volaMult
bearFVG = showBear and high < low[2] and (low[2] - high) > vola * volaMult
volumes = request.security_lower_tf(syminfo.tickerid, ltf, volume)
prices = request.security_lower_tf(syminfo.tickerid, ltf, close)
prevVolumes = array.new_float()
prevPrices = array.new_float()
if needVolProfile
if prices.size()[1] > 0 and not na(prices[1])
for i = 0 to prices.size()[1]-1
prevPrices.push(array.get(prices[1] , i))
if volumes.size()[1] > 0 and not na(volumes[1])
for i = 0 to volumes.size()[1]-1
prevVolumes.push(array.get(volumes[1] , i))
//────────────────────────────────────────────────────────────────────── Data Structures
var box[] gapBoxes = array.new<box>()
var int[] gapLefts = array.new<int>()
var float[] gapTops = array.new<float>()
var float[] gapBots = array.new<float>()
var color[] gapCols = array.new<color>()
var int[] gapTypes = array.new<int>()
var int[] gapFilled= array.new<int>()
var matrix<float> fvgVols = matrix.new<float>(0, bincount)
var matrix<box> fvgBins = matrix.new<box>(0, bincount)
var box[] lowVolBoxes = array.new<box>()
var int[] lowVolIdxs = array.new<int>()
bool bullMit = false
bool bearMit = false
//────────────────────────────────────────────────────────────────────── Overlap Check Function
f_can_create(float tNew, float bNew) =>
bool ok = true
if gapBoxes.size() > 0
for j = 0 to array.size(gapBoxes) - 1
if na(array.get(gapFilled, j))
if (tNew > array.get(gapBots, j)) and (bNew < array.get(gapTops, j))
ok := false
break
ok
//────────────────────────────────────────────────────────────────────── New Gap Creation Function
f_new(bool isBull) =>
l = bar_index - 1
r = bar_index
t = isBull ? low : low[2]
b = isBull ? high[2] : high
c = chart.fg_color
bgTransp = showGapZone ? 90 : 100
brdTransp = showGapZone ? 90 : 100
big = box.new(l, t, r, b,
bgcolor = color.new(c, bgTransp),
border_color = color.new(c, brdTransp),
extend = extend.none, text = na(prevVolumes.sum()) ? "" : str.tostring(prevVolumes.sum(), format.volume), text_size = size.tiny, text_color = chart.fg_color, text_halign = text.align_right, text_valign = text.align_bottom)
array.unshift(gapBoxes , big)
array.unshift(gapLefts , l)
array.unshift(gapTops , t)
array.unshift(gapBots , b)
array.unshift(gapCols , c)
array.unshift(gapTypes , isBull ? 1 : -1)
array.unshift(gapFilled, na)
array.unshift(lowVolBoxes, na)
array.unshift(lowVolIdxs , na)
if needVolProfile
range_ = t - b
seg = range_ / bincount
bounds = array.new_float()
vols = array.new_float()
for i = 0 to bincount
bounds.push(b + i * seg)
for i = 0 to bincount-1
total_vol = 0.0
if prevPrices.size() == prevVolumes.size() and prevVolumes.size() > 0
for j = 0 to prevPrices.size()-1
if prevPrices.get(j) > bounds.get(i) and prevPrices.get(j) < bounds.get(i+1)
total_vol += prevVolumes.get(j)
vols.push(total_vol)
longest = vols.max()
maximum = 30
float[] volRow = array.new_float(bincount, 0.0)
box[] boxRow = array.new<box>()
seg_len = seg
for idx = 0 to bincount-1
volVal = vols.get(idx)
array.set(volRow, idx, volVal)
if showProfile and vols.sum() > 0 and longest > 0
binBot = bounds.get(idx)
binTop = bounds.get(idx + 1)
rBin = l + int(maximum * profileScale * (volVal / longest))
bBox = box.new(l, binTop, rBin, binBot, color.new(chart.fg_color, 90), bgcolor = color.new(chart.fg_color, profgrad ? (90*(1-0.7*((volVal / longest)))) : 50))
array.push(boxRow, bBox)
else
array.push(boxRow, na)
matrix.add_row(fvgVols, 0, volRow)
matrix.add_row(fvgBins, 0, boxRow)
winSize = math.min(lvWindow, bincount)
float minSum = 1e10
int minIdx = 0
for win = 0 to (bincount - winSize)
curSum = 0.0
for k = 0 to winSize - 1
curSum += vols.get(win + k)
if curSum < minSum
minSum := curSum
minIdx := win
lvBot = bounds.get(minIdx)
lvTop = bounds.get(minIdx + winSize)
lvColBase = isBull ? bullCol : bearCol
lvColBg = color.new(lvColBase, 85)
lvColBrd = color.new(lvColBase, 100)
if showLowVolZone
lvBox = box.new(l, lvTop, r, lvBot, bgcolor = lvColBg, border_color = lvColBrd)
array.set(lowVolBoxes, 0, lvBox)
array.set(lowVolIdxs , 0, minIdx)
else
matrix.add_row(fvgVols, 0, array.new_float(bincount, 0.0))
matrix.add_row(fvgBins, 0, array.new<box>(bincount))
//────────────────────────────────────────────────────────────────────── Gap Management and Rendering
if array.size(gapBoxes) > 0
for i = array.size(gapBoxes) - 1 to 0
l = array.get(gapLefts , i)
t = array.get(gapTops , i)
b = array.get(gapBots , i)
typ = array.get(gapTypes , i)
fill= array.get(gapFilled, i)
age = bar_index - l
if na(fill)
done = typ == 1 ? (low <= b) : (high >= t)
if done
array.set(gapFilled, i, bar_index)
fill := bar_index
if typ == 1
bullMit := true
else
bearMit := true
if age > maxBoxAge
if array.size(lowVolBoxes) > i
if not na(array.get(lowVolBoxes, i))
array.get(lowVolBoxes, i).delete()
array.remove(lowVolBoxes, i)
array.remove(lowVolIdxs , i)
if matrix.rows(fvgBins) > i
// Delete individual bin boxes only when they exist (are not na)
for j = 0 to bincount-1
binDel = matrix.get(fvgBins, i, j)
if not na(binDel)
binDel.delete()
matrix.remove_row(fvgBins, i)
matrix.remove_row(fvgVols, i)
array.get(gapBoxes, i).delete()
array.remove(gapBoxes, i), array.remove(gapLefts, i),
array.remove(gapTops, i), array.remove(gapBots, i),
array.remove(gapCols, i), array.remove(gapTypes, i),
array.remove(gapFilled, i)
continue
rEdge = na(fill) ? bar_index : fill
array.get(gapBoxes, i).set_right(rEdge)
if showLowVolZone and array.size(lowVolBoxes) > i and not na(array.get(lowVolBoxes, i))
lvBox_upd = array.get(lowVolBoxes, i)
lvBox_upd.set_right(rEdge)
idxWin = array.get(lowVolIdxs, i)
if not na(idxWin)
baseBot = b + idxWin * ((t - b) / bincount)
baseTop = baseBot + lvWindow * ((t - b) / bincount)
lvBox_upd.set_top(baseTop)
lvBox_upd.set_bottom(baseBot)
if showProfile and matrix.rows(fvgBins) > i
width = rEdge - l
if width < 1
width := 1
longVol = 0.0
for j = 0 to bincount-1
longVol := math.max(longVol, matrix.get(fvgVols, i, j))
if longVol > 0
seg_len = (t - b) / bincount
for j = 0 to bincount-1
volVal = matrix.get(fvgVols, i, j)
rightPx = l + int(width * profileScale * (volVal / longVol))
binBox = matrix.get(fvgBins, i, j)
if not na(binBox)
binBox.set_left(l)
binBox.set_right(rightPx)
binBox.set_top( b + (j + 1) * seg_len )
binBox.set_bottom(b + j * seg_len )
//────────────────────────────────────────────────────────────────────── New Gap Detection
if bullFVG
tProp = low
bProp = high[2]
if not preventOverlap or f_can_create(tProp, bProp)
f_new(true)
if bearFVG
tProp = low[2]
bProp = high
if not preventOverlap or f_can_create(tProp, bProp)
f_new(false)
//────────────────────────────────────────────────────────────────────── Alerts
alertcondition(bullFVG, "Bullish FVG", "Bullish fair-value gap detected")
alertcondition(bearFVG, "Bearish FVG", "Bearish fair-value gap detected")
alertcondition(bullMit, "Bullish FVG Mitigated", "Bullish fair-value gap mitigated")
alertcondition(bearMit, "Bearish FVG Mitigated", "Bearish fair-value gap mitigated")
//────────────────────────────────────────────────────────────────────── Volume Availability Check & Warning Table
volumeAvailable = not na(volume)
if barstate.islast and not volumeAvailable
var volWarnTable = table.new(position = position.top_right, columns = 1, rows = 1, bgcolor = bearCol, border_width = 1, border_color = chart.fg_color, frame_color = chart.fg_color, frame_width = 1)
table.cell(volWarnTable, 0, 0, "Volume not available\nIndicator might not work as expected", text_color = chart.fg_color, text_halign = text.align_center, text_size = size.small)