本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。
//@version=6
// ══════════════════════════════════════════════════════════════════════════════════════════════════ //
//# * ══════════════════════════════════════════════════════════════════════════════════════════════
//# *
//# * Study : Fair Value Gaps
//# * Author : © dgtrd
//# *
//# * Revision History
//# * Release : Oct 13, 2025
//# *
//# * ══════════════════════════════════════════════════════════════════════════════════════════════
// ══════════════════════════════════════════════════════════════════════════════════════════════════ //
indicator('Fair Value Gaps by DGT', 'FVGs · ☼☾', true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500)
//---------------------------------------------------------------------------------------------------------------------//
// Settings
//---------------------------------------------------------------------------------------------------------------------//
display = display.all - display.status_line
fvgGR = 'Fair Value Gaps #1, Chart/HTF'
fvgTip1 = 'Select the timeframe from which Fair Value Gaps (FVGs) will be detected.\n' +
'• Chart — uses the current chart timeframe.\n' +
'• Higher timeframes — plots FVGs derived from the selected timeframe above the chart.\n\n' +
'If a lower timeframe than the chart is selected, the script automatically defaults to the chart timeframe.'
fvgFillTip1 = 'Defines how a Fair Value Gap (FVG) is considered filled:\n' +
'• Any Touch — when price (wick or body) touches any part of the imbalance zone.\n' +
'• Midpoint Reached — when price trades to at least the midpoint (50%) of the zone.\n' +
'• Wick Sweep — when a wick fully passes through the entire FVG and returns inside the zone.\n' +
'• Body Beyond — when the candle body closes beyond the opposite edge of the FVG, confirming full repair.'
fvgLBTip1 = 'Controls the visibility of Fair Value Gap (FVG) labels.\n\n' +
'• **Show** — displays labels directly on the chart. Each label indicates the timeframe and fill progress of the zone.\n' +
'• **Hide** — hides on-chart labels, but tooltips remain accessible by hovering over the midpoint of each FVG zone.'
fvgSH1 = input.bool(true, 'Fair Value Gaps #1, Chart/HTF', inline = 'fvg1', group = fvgGR)
fvgTF1 = input.string('Chart', '', options = ['Chart', '5 Minutes', '15 Minutes', '1 Hour', '4 Hours', '1 Day', '1 Week', '1 Month'], inline = 'fvg1', group = fvgGR, tooltip = fvgTip1, display = display)
fvgFill1 = input.string('Wick Sweep', ' FVG #1 Fill Method', options = ['Any Touch', 'Midpoint Reached', 'Wick Sweep', 'Body Beyond'], display = display, tooltip = fvgFillTip1, group = fvgGR)
fvgCb1 = input.color(#00B8D9, ' FVG #1 Zones ', inline = 'fvg', group = fvgGR)
fvgCs1 = input.color(#FF8B00, '', inline = 'fvg', group = fvgGR)
fvgLB1 = input.string('Show', ' FVG #1 Labels', options = ['Show', 'Hide'], tooltip = fvgLBTip1, display = display, group = fvgGR)
maxBars = input.int(360, ' Max Bars Back', minval = 1, step = 20, group = fvgGR, display = display, tooltip = 'This determines how far back the script will analyze for Fair Value Gaps.')
fvgGR2 = 'Fair Value Gaps #, Chart/HTF'
fvgSH2 = input.bool(false, 'Fair Value Gaps #2, Chart/HTF', inline = 'fvg2', group = fvgGR2)
fvgTF2 = input.string('1 Hour', '', options = ['Chart', '5 Minutes', '15 Minutes', '1 Hour', '4 Hours', '1 Day', '1 Week', '1 Month'], inline = 'fvg2', group = fvgGR2, tooltip = fvgTip1, display = display)
fvgFill2 = input.string('Wick Sweep', ' FVG #2 Fill Method', options = ['Any Touch', 'Midpoint Reached', 'Wick Sweep', 'Body Beyond'], display = display, tooltip = fvgFillTip1, group = fvgGR2)
fvgCb2 = input.color(#6EE7B7, ' FVG #2 Zones ', inline = 'fvg', group = fvgGR2)
fvgCs2 = input.color(#FF6B6B, '', inline = 'fvg', group = fvgGR2)
fvgLB2 = input.string('Hide', ' FVG #2 Labels', options = ['Show', 'Hide'], tooltip = fvgLBTip1, display = display, group = fvgGR2)
maxBars2 = input.int(360, ' Max Bars Back', minval = 1, step = 20, group = fvgGR2, display = display, tooltip = 'This determines how far back the script will analyze for Fair Value Gaps.')
fvgGR3 = 'Fair Value Gaps #3, LTF'
fvgTip3 = 'Intrabar timeframe (lower timeframe, LTF) setting option. ' +
'When set to "Auto", the lower timeframe is determined by the following algorithm:\n' +
' Chart Timeframe → Lower Timeframe\n' +
'\t\t<= 3 min\t\t→ 15 sec\n' +
'\t\t<= 30 min\t\t→ 1 min\n' +
'\t\t<= 2 hour\t\t→ 5 min\n' +
'\t\t<= 4 hour\t\t→ 15 min\n' +
'\t\t<= 1 day\t\t→ 1 hour\n' +
'\t\t<= 1 week\t\t→ 4 hour\n' +
'\t\t<= 1 month\t→ 1 day\n' +
'\t\t> 1 month\t→ 1 week'
fvgSH3 = input.bool(false, 'Fair Value Gaps #3, LTF', inline = 'fvg3', group = fvgGR3)
fvgTF3 = input.timeframe('Auto', '', options = ['Auto', '1S', '5S', '10S', '15S', '30S', '1m', '3m', '5m', '15m', '30m', '45m', '1H', '2H', '3H', '4H', 'D'], inline = 'fvg3', group = fvgGR3, tooltip = fvgTip3)
fvgThTip = 'Checks the significance of the detected fair value gap compared to a fixed-length average true range. ' + 'Setting the value to 0 means that no filtering will be applied, whereas values greater than zero determine the filtering threshold value'
fvgTH3 = input.float(.7, ' FVG Filter Threshold', minval = 0, step = .1, group = fvgGR3, tooltip = fvgThTip)
fvgCb3 = input.color(#8BC34A, ' FVG #3 Zones ', inline = 'VD', group = fvgGR3)
fvgCs3 = input.color(#E91E63, '', inline = 'VD', group = fvgGR3)
fvgLBTip3 = 'Controls the visibility of lower-timeframe Fair Value Gap (FVG) labels.\n\n' +
'• **Show** — displays labels for each detected LTF FVG, indicating the source lower timeframe of the zone.\n' +
'• **Hide** — hides the on-chart labels, while tooltips remain available when hovering over the midpoint of each LTF FVG zone.'
fvgLB3 = input.string('Hide', ' FVG #3 Labels', options = ['Show', 'Hide'], tooltip = fvgLBTip3, display = display, group = fvgGR3)
//---------------------------------------------------------------------------------------------------------------------//
// User Defined Types
//---------------------------------------------------------------------------------------------------------------------//
type BAR
float open = open
float high = high
float low = low
float close = close
int index = bar_index
float ltfHigh
float ltfHigh1
float ltfHigh2
float ltfLow
float ltfLow1
float ltfLow2
float ltfClose
float ltfClose1
type HTFBar
float price = na
int index = na
type HTFBarOHLC
HTFBar open
HTFBar high
HTFBar low
HTFBar close
type FVG
label lbTxt
line lnTop
line lnAvg
line lnBtm
float mitPrice
float mit
string txt
BAR bar = BAR.new()
//---------------------------------------------------------------------------------------------------------------------//
// Functions / Methods
//---------------------------------------------------------------------------------------------------------------------//
ltfTF(_t) =>
str.contains(_t, 'S') or str.contains(_t, 'D') or str.contains(_t, 'W') or str.contains(_t, 'M') ? _t :
str.contains(_t, 'm') ? str.replace(_t, 'm', '', 0) : str.contains(_t, 'H') ? str.tostring(str.tonumber(str.replace(_t, 'H', '', 0)) * 60) : '12M'
ltfTF() =>
int tfInMs = timeframe.in_seconds(timeframe.period)
int mInMS = 60
switch
tfInMs <= 1 * mInMS => '5S'
tfInMs <= 3 * mInMS => '15S'
tfInMs <= 30 * mInMS => '1'
tfInMs <= 120 * mInMS => '5'
tfInMs <= 240 * mInMS => '15'
tfInMs <= 1440 * mInMS => '60'
tfInMs <= 7 * 1440 * mInMS => '240'
tfInMs <= 31 * 1440 * mInMS => 'D'
=> 'W'
htfTF(usrTF) =>
float chartTFin = timeframe.in_seconds() / 60
switch
usrTF == '5 Minutes' and chartTFin <= 5 => '5'
usrTF == '15 Minutes' and chartTFin <= 15 => '15'
usrTF == '1 Hour' and chartTFin <= 60 => '60'
usrTF == '4 Hours' and chartTFin <= 240 => '240'
usrTF == '1 Day' and timeframe.isintraday => 'D'
usrTF == '1 Week' and (timeframe.isdaily or timeframe.isintraday) => 'W'
usrTF == '1 Month' and (timeframe.isweekly or timeframe.isdaily or timeframe.isintraday) => 'M'
=> timeframe.period
updateFvg(fvgArray, fill, side, fvgLb) =>
var string fiiled = ''
if fvgArray.size() > 0
for i = array.size(fvgArray) - 1 to 0
this = array.get(fvgArray, i)
if side == "bull" ? (fill == 'Body Beyond' ? bar.close : bar.low) <= this.mitPrice : (fill == 'Body Beyond' ? bar.close : bar.high) > this.mitPrice
fvgArray.remove(i)
this.lbTxt.delete()
this.lnTop.delete()
this.lnAvg.delete()
this.lnBtm.delete()
else
this.lbTxt.set_x(bar.index + 1)
this.lnTop.set_x2(bar.index + 1)
this.lnAvg.set_x2(bar.index + 1)
this.lnBtm.set_x2(bar.index + 1)
fillRatio = switch side
"bull" => (this.lnTop.get_y1() - bar.low) / (this.lnTop.get_y1() - this.lnBtm.get_y1())
"bear" => (bar.high - this.lnBtm.get_y1()) / (this.lnTop.get_y1() - this.lnBtm.get_y1())
=> 0.0
if fillRatio > this.mit
this.mit := fillRatio
this.lbTxt.set_text( fvgLb == 'Show' ? this.txt + str.format("{0,number,(#.#%)}", fillRatio) : '')
renderFvg(show, tfIn, fill, cBull, cBear, fvgLb) =>
if show
var array<FVG> bullFvgArray = array.new<FVG>()
var array<FVG> bearFvgArray = array.new<FVG>()
var array<HTFBarOHLC> barArray = array.new<HTFBarOHLC>()
var HTFBar barOpen = HTFBar.new(bar.open, bar.index)
var HTFBar barHigh = HTFBar.new(bar.high, bar.index)
var HTFBar barLow = HTFBar.new(bar.low, bar.index)
var HTFBar barClose = HTFBar.new(bar.close, bar.index)
var barHist = HTFBarOHLC.new(barOpen, barHigh, barLow, barClose)
var int bullBarIndexEnd = na
var int bullBarIndexEnd1 = na
var int bearBarIndexEnd = na
var int bearBarIndexEnd1 = na
var bool bullProcess = false
var bool bullCondition = false
var bool bearProcess = false
var bool bearCondition = false
var bool bullG = false
var bool bearG = false
string tf = htfTF(tfIn)
fvgTf = tf == timeframe.period
//log.info("yaz_kizim {0} {1}", htfATR, tf)
if timeframe.change(tf)
bullProcess := false
bearProcess := false
if barArray.size() > 2
barArray.pop()
barArray.unshift(barHist)
barOpen := HTFBar.new(bar.open, bar.index)
barHigh := HTFBar.new(bar.high, bar.index)
barLow := HTFBar.new(bar.low, bar.index)
barClose := HTFBar.new(bar.close, bar.index)
barHist := HTFBarOHLC.new(barOpen, barHigh, barLow, barClose)
bullBarIndexEnd1 := bullBarIndexEnd
bullBarIndexEnd := bar.index
bearBarIndexEnd1 := bearBarIndexEnd
bearBarIndexEnd := bar.index
bearBarIndexEnd
else
if bar.high > barHigh.price
barHigh.price := bar.high
barHigh.index := bar.index
barHigh.index
if bar.low < barLow.price
barLow.price := bar.low
barLow.index := bar.index
barLow.index
barClose.price := bar.close
barClose.index := bar.index
if barArray.size() > 2
if bar.low < barArray.get(1).high.price
bullBarIndexEnd := bar.index
bullBarIndexEnd
if bar.high > barArray.get(1).low.price
bearBarIndexEnd := bar.index
bearBarIndexEnd
if barArray.size() > 2
bullG := barArray.get(0).low.price > barArray.get(1).high.price or barArray.get(1).low.price > barArray.get(2).high.price
bullCondition := barArray.get(0).low.price > barArray.get(2).high.price and barArray.get(1).close.price > barArray.get(2).high.price and not bullG
bearG := barArray.get(0).high.price < barArray.get(1).low.price or barArray.get(1).high.price < barArray.get(2).low.price
bearCondition := barArray.get(0).high.price < barArray.get(2).low.price and barArray.get(1).close.price < barArray.get(2).low.price and not bearG
if bullCondition and not bullProcess
bullProcess := true
bullFvgArray.unshift(
FVG.new(
label.new(bar.index + 1, math.avg(barArray.get(0).low.price, barArray.get(2).high.price), fvgLb == 'Show' ? fvgTf ? 'Chart(' + tf + ') FVG' : 'HTF(' + tf + ') FVG' : '', xloc.bar_index, color = color(na), style = label.style_label_left, textcolor = cBull, size = size.small,
tooltip = (fvgTf ? 'Chart(' + tf + ') Bullish FVG' : 'HTF(' + tf + ') Bullish FVG') + '\n\nZone Top · ' + str.format("{0, number, #,###.##}", barArray.get(0).low.price) + '\nZone Mid · ' + str.format("{0, number, #,###.##}", math.avg(barArray.get(0).low.price, barArray.get(2).high.price)) + '\nZone Btm · ' + str.format("{0, number, #,###.##}", barArray.get(2).high.price) ),
line.new(barArray.get(0).low.index, barArray.get(0).low.price, bar.index + 1, barArray.get(0).low.price, color = color.new(cBull, 65)),
line.new(math.ceil(math.avg(barArray.get(0).low.index, barArray.get(2).high.index)), math.avg(barArray.get(0).low.price, barArray.get(2).high.price), bar.index + 1, math.avg(barArray.get(0).low.price, barArray.get(2).high.price), style = line.style_dotted, color = color.new(cBull, 25)),
line.new(barArray.get(2).high.index, barArray.get(2).high.price, bar.index + 1, barArray.get(2).high.price, color = color.new(cBull, 50), width = 2),
fill == 'Midpoint Reached' ? math.avg(barArray.get(2).low.price, barArray.get(0).high.price) : fill == 'Any Touch' ? barArray.get(0).low.price : barArray.get(2).high.price, 0, fvgTf ? 'Chart(' + tf + ') FVG' : 'HTF(' + tf + ') FVG')
)
first = bullFvgArray.first()
linefill.new(first.lnTop, first.lnBtm, color.new(cBull, 89))
if bearCondition and not bearProcess
bearProcess := true
bearFvgArray.unshift(
FVG.new(
label.new(bar.index + 1, math.avg(barArray.get(2).low.price, barArray.get(0).high.price), fvgLb == 'Show' ? fvgTf ? 'Chart(' + tf + ') FVG' : 'HTF(' + tf + ') FVG' : '', xloc.bar_index, color = color(na), style = label.style_label_left, textcolor = cBear, size = size.small,
tooltip = (fvgTf ? 'Chart(' + tf + ') Bearish FVG' : 'HTF(' + tf + ') Bearish FVG') + '\n\nZone Top · ' + str.format("{0, number, #,###.##}", barArray.get(2).low.price) + '\nZone Mid · ' + str.format("{0, number, #,###.##}", math.avg(barArray.get(2).low.price, barArray.get(0).high.price)) + '\nZone Btm · ' + str.format("{0, number, #,###.##}", barArray.get(0).high.price) ),
line.new(barArray.get(2).low.index, barArray.get(2).low.price, bar.index + 1, barArray.get(2).low.price, color = color.new(cBear, 50), width = 2),
line.new(math.ceil(math.avg(barArray.get(2).low.index, barArray.get(0).high.index)), math.avg(barArray.get(2).low.price, barArray.get(0).high.price), bar.index + 1, math.avg(barArray.get(2).low.price, barArray.get(0).high.price), style = line.style_dotted, color = color.new(cBear, 25)),
line.new(barArray.get(0).high.index, barArray.get(0).high.price, bar.index + 1, barArray.get(0).high.price, color = color.new(cBear, 65)),
fill == 'Midpoint Reached' ? math.avg(barArray.get(2).low.price, barArray.get(0).high.price) : fill == 'Any Touch' ? barArray.get(0).high.price : barArray.get(2).low.price, 0, fvgTf ? 'Chart(' + tf + ') FVG' : 'HTF(' + tf + ') FVG')
)
first = bearFvgArray.first()
linefill.new(first.lnTop, first.lnBtm, color.new(cBear, 89))
updateFvg(bullFvgArray, fill, 'bull', fvgLb)
updateFvg(bearFvgArray, fill, 'bear', fvgLb)
//---------------------------------------------------------------------------------------------------------------------//
// Calculations
//---------------------------------------------------------------------------------------------------------------------//
var fvgLTFbx = array.new_box()
var fvgLTFlb = array.new_label()
ltf = fvgTF3 == 'Auto' ? ltfTF() : ltfTF(fvgTF3)
[aO, aH, aL, aC, atr] = request.security_lower_tf(syminfo.tickerid, ltf, [bar.open, bar.high, bar.low, bar.close, ta.atr(13)])
if fvgSH3 and timeframe.change(timeframe.period) and array.size(aH) > 0
for i = 0 to aH.size() - 1 by 1
bar.ltfClose1 := bar.ltfClose
bar.ltfClose := aC.get(i)
bar.ltfHigh2 := bar.ltfHigh1
bar.ltfHigh1 := bar.ltfHigh
bar.ltfHigh := aH.get(i)
bar.ltfLow2 := bar.ltfLow1
bar.ltfLow1 := bar.ltfLow
bar.ltfLow := aL.get(i)
bull = bar.ltfLow - bar.ltfHigh2 > atr.get(i) * fvgTH3 and bar.ltfLow > bar.ltfHigh2 and bar.ltfClose1 > bar.ltfHigh2
if bull
fvgLTFbx.push(box.new(bar.index, bar.ltfLow, bar.index + 1, bar.ltfHigh2, color.new(fvgCb3, 73), bgcolor = color.new(fvgCb3, 73)))
fvgLTFlb.push(
label.new(bar.index + 1, math.avg(bar.ltfLow, bar.ltfHigh2), fvgLB3 == 'Show' ? 'LTF(' + ltf + ') FVG' : '', xloc.bar_index, color = color(na), style = label.style_label_left, textcolor = fvgCb3, size = size.small,
tooltip = 'LTF(' + ltf + ') Bullish FVG' )
)
bear = bar.ltfLow2 - bar.ltfHigh > atr.get(i) * fvgTH3 and bar.ltfHigh < bar.ltfLow2 and bar.ltfClose1 < bar.ltfLow2
if bear
fvgLTFbx.push(box.new(bar.index, bar.ltfLow2, bar.index + 1, bar.ltfHigh, color.new(fvgCs3, 73), bgcolor = color.new(fvgCs3, 73)))
fvgLTFlb.push(
label.new(bar.index + 1, math.avg(bar.ltfLow2, bar.ltfHigh), fvgLB3 == 'Show' ? 'LTF(' + ltf + ') FVG' : '', xloc.bar_index, color = color(na), style = label.style_label_left, textcolor = fvgCs3, size = size.small,
tooltip = 'LTF(' + ltf + ') Bearish FVG' )
)
if fvgLTFbx.size() > 0
qt = fvgLTFbx.size()
for bn = qt - 1 to 0 by 1
if bn < fvgLTFbx.size()
cBX = fvgLTFbx.get(bn)
cLB = fvgLTFlb.get(bn)
tBX = cBX.get_top()
bBX = cBX.get_bottom()
if bar.ltfHigh >= tBX and bar.ltfLow <= bBX
cBX.delete()
fvgLTFbx.remove(bn)
fvgLTFlb.remove(bn)
cLB.delete()
else
if bar.ltfHigh > bBX and bar.ltfHigh < tBX
cBX.set_bottom(bar.ltfHigh)
cLB.set_y(math.avg(cBX.get_top(), bar.ltfHigh))
if bar.ltfLow < tBX and bar.ltfLow > bBX
cBX.set_top(bar.ltfLow)
cLB.set_y(math.avg(cBX.get_bottom(), bar.ltfLow))
if fvgLTFbx.size() > 250
cBX = fvgLTFbx.shift()
cLB = fvgLTFlb.shift()
cBX.delete()
cLB.delete()
if fvgLTFbx.size() > 0
qt = fvgLTFbx.size()
for bn = qt - 1 to 0 by 1
if bn < fvgLTFbx.size()
cBX = fvgLTFbx.get(bn)
cLB = fvgLTFlb.get(bn)
tBX = cBX.get_top()
bBX = cBX.get_bottom()
if bar.high > tBX and bar.low < bBX and bar.index > cBX.get_left()
fvgLTFbx.remove(bn)
cBX.delete()
fvgLTFlb.remove(bn)
cLB.delete()
else
cBX.set_right(bar.index + 1)
cBX.set_border_color(na)
cLB.set_x(bar.index + 1)
if last_bar_index - bar_index <= maxBars and fvgSH1
renderFvg(fvgSH1, fvgTF1, fvgFill1, fvgCb1, fvgCs1, fvgLB1)
if last_bar_index - bar_index <= maxBars2 and fvgSH2
renderFvg(fvgSH2, fvgTF2, fvgFill2, fvgCb2, fvgCs2, fvgLB2)
table logo = table.new(position.bottom_right, 1, 1)
if barstate.isfirst
table.cell(logo, 0, 0, '☼☾ ', text_size = size.normal, text_color = color.teal)