本页源码来自 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/
// © FluxCharts
//@version=5
const bool DEBUG = false
const int maxBoxesCount = 500
const int showLastXFVGs = 50
const int extendLastXFVGsCount = 3
const int minimumFVGSize = 2
const float overlapThresholdPercentage = 0.01
const int maxFVGDistanceToCurrent = 4000
indicator(title = 'Fair Value Gaps (Volumetric) | Flux Charts', shorttitle = "FVG (Volumetric) | Flux Charts", overlay = true, max_boxes_count = maxBoxesCount, max_labels_count = maxBoxesCount, max_lines_count = maxBoxesCount, max_bars_back = 5000)
fvgEndMethod = input.string("Wick", "Zone Invalidation", options = ["Wick", "Close"], group = "General Configuration")
fvgFilterMethod = input.string("Average Range", "Zone Filtering", options = ["Average Range", "Volume Threshold"], group = "General Configuration")
volumeThresholdPercent = input.int(30, "Volume Threshold %", group = "General Configuration", tooltip = "Only taken into calculation when filter method is Volume Threshold.", minval = 20, maxval = 200)
fvgBars = input.string("Same Type", "FVG Detection", options = ["Same Type", "All"], tooltip = "Same Type -> All 3 bars that formed the FVG should be the same type. (Bullish / Bearish) \n\nAll -> Bar types may vary between bullish / bearish.", group = "General Configuration")
fvgSensitivityText = input.string("Normal", "Detection Sensitivity", options = ["High", "Normal", "Low"], group = "General Configuration")
combineFVGs = DEBUG ? input.bool(true, "Combine Zones", group = "General Configuration") : true
deleteUntouched = DEBUG ? input.bool(true, "", group = "General Configuration", inline = "deleteUntouched") : true
deleteUntouchedAfterXBars = DEBUG ? input.int(200, "Delete Untouched Zones After", group = "General Configuration", minval = 5, maxval = 500, inline = "deleteUntouched") : 200
showInvalidated = input.bool(true, "Show Historic Zones", group = "General Configuration")
bullColor = input.color(#08998180, "Bull Color", group = "Style", inline = "BBcolors")
bearColor = input.color(#f2364580, "Bear Color", group = "Style", inline = "BBcolors")
textColor = input.color(#ffffff80, "Text Color", group = "Style", inline = "BBcolors")
extendZonesBy = DEBUG ? input.int(15, "Extend Zones", group = "Style", minval = 1, maxval = 30, inline = "ExtendZones") : 15
extendZonesDynamic = DEBUG ? input.bool(true, "Dynamic", group = "Style", inline = "ExtendZones") : true
extendLastFVGs = DEBUG ? input.bool(true, "Extend Last Zones", group = "Style") : true
changeCombinedFVGsColor = DEBUG ? input.bool(true, "Change Combined Zones Color", group = "Style", inline = "CombinedColor") : false
combinedText = DEBUG ? input.bool(true, "Combined Text", group = "Style", inline = "CombinedColor") : true
combinedColor = DEBUG ? input.color(#fff70080, DEBUG ? "" : "Combined Zone Color", group = "Style", inline = "CombinedColor") : #fff70080
startZoneFrom = input.string("Last Bar", "Start Zones From", options = ["First Bar", "Last Bar"], group = "Style")
volumeBarsPlace = DEBUG ? input.string("Left", "Show Volume Bars At", options = ["Left", "Right"], group = "Style", inline = "volumebars") : "Left"
mirrorVolumeBars = DEBUG ? input.bool(true, "Mirror Volume Bars", group = "Style", inline = "volumebars") : true
volumeBarsLeftSide = (volumeBarsPlace == "Left")
fvgSensitivity = fvgSensitivityText == "High" ? 1 : fvgSensitivityText == "Normal" ? 1.25 : 1.5
type FVG
float max = na
float min = na
bool isBull = na
int t = na
float totalVolume = na
int startBarIndex = na
int endBarIndex = na
bool extendInfinite = false
bool combined = false
bool disabled = false
bool isRendered = false
float lowVolume = na
float highVolume = na
box fvgBox = na
box fvgBoxText = na
box fvgBoxPositive = na
box fvgBoxNegative = na
line fvgSeperator = na
line fvgTextSeperator = na
createFVG (h,l,bull,t,tv) =>
FVG newFVG = FVG.new(h, l, bull, t, tv)
newFVG
safeDeleteFVG (FVG fvg) =>
fvg.isRendered := false
box.delete(fvg.fvgBox)
box.delete(fvg.fvgBoxText)
box.delete(fvg.fvgBoxPositive)
box.delete(fvg.fvgBoxNegative)
line.delete(fvg.fvgSeperator)
line.delete(fvg.fvgTextSeperator)
var FVG[] fvgList = array.new<FVG>()
moveLine(_line, _x, _y, _x2) =>
line.set_xy1(_line, _x, _y)
line.set_xy2(_line, _x2, _y)
moveBox (_box, _topLeftX, _topLeftY, _bottomRightX, _bottomRightY) =>
box.set_lefttop(_box, _topLeftX, _topLeftY)
box.set_rightbottom(_box, _bottomRightX, _bottomRightY)
colorWithTransparency (colorF, transparencyX) =>
color.new(colorF, color.t(colorF) * transparencyX)
createFVGBox (boxColor, transparencyX = 1.0, xlocType = xloc.bar_index) =>
box.new(na, na, na, na, text_size = size.normal, xloc = xlocType, extend = extend.none, bgcolor = colorWithTransparency(boxColor, transparencyX), text_color = textColor, text_halign = text.align_center, border_color = #00000000)
renderFVG (FVG fvg) =>
fvg.isRendered := true
fvg.fvgBox := createFVGBox((fvg.combined and changeCombinedFVGsColor) ? combinedColor : fvg.isBull ? bullColor : bearColor, 1.5)
fvg.fvgBoxPositive := createFVGBox(bullColor)
fvg.fvgBoxNegative := createFVGBox(bearColor)
fvg.fvgBoxText := createFVGBox(color.new(color.white, 100))
fvg.fvgSeperator := line.new(na,na,na,na,xloc.bar_index,extend.none,textColor,line.style_dashed,1)
fvg.fvgTextSeperator := line.new(na,na,na,na,xloc.bar_index,extend.none,textColor,line.style_solid,1)
zoneSize = extendZonesDynamic ? na(fvg.endBarIndex) ? extendZonesBy : (fvg.endBarIndex - fvg.startBarIndex) : extendZonesBy
if na(fvg.endBarIndex) and fvg.extendInfinite
zoneSize := bar_index - fvg.startBarIndex
startX = volumeBarsLeftSide ? fvg.startBarIndex : fvg.startBarIndex + zoneSize - zoneSize / 3
maxEndX = volumeBarsLeftSide ? fvg.startBarIndex + zoneSize / 3 : fvg.startBarIndex + zoneSize
moveBox(fvg.fvgBox, fvg.startBarIndex, fvg.max, fvg.startBarIndex + zoneSize, fvg.min)
moveBox(fvg.fvgBoxText, volumeBarsLeftSide ? maxEndX : fvg.startBarIndex, fvg.max, volumeBarsLeftSide ? fvg.startBarIndex + zoneSize : startX, fvg.min)
percentage = int((math.min(fvg.highVolume, fvg.lowVolume) / math.max(fvg.highVolume, fvg.lowVolume)) * 100.0)
box.set_text(fvg.fvgBoxText, str.tostring(fvg.totalVolume, format.volume) + " (" + str.tostring(percentage) + "%)" + (combinedText and fvg.combined ? "\n[Combined]" : ""))
if fvg.combined and not changeCombinedFVGsColor
fvg.fvgBox.set_bgcolor(colorWithTransparency(fvg.isBull ? bullColor : bearColor, 1.1))
showHighLowBoxText = false
curEndXHigh = int(math.ceil((fvg.highVolume / fvg.totalVolume) * (maxEndX - startX) + startX))
curEndXLow = int(math.ceil((fvg.lowVolume / fvg.totalVolume) * (maxEndX - startX) + startX))
moveBox(fvg.fvgBoxPositive, mirrorVolumeBars ? startX : curEndXLow, fvg.max, mirrorVolumeBars ? curEndXHigh : maxEndX, (fvg.min + fvg.max) / 2)
box.set_text(fvg.fvgBoxPositive, showHighLowBoxText ? str.tostring(fvg.highVolume, format.volume) : "")
moveBox(fvg.fvgBoxNegative, mirrorVolumeBars ? startX : curEndXHigh, fvg.min, mirrorVolumeBars ? curEndXLow : maxEndX, (fvg.min + fvg.max) / 2)
box.set_text(fvg.fvgBoxNegative, showHighLowBoxText ? str.tostring(fvg.lowVolume, format.volume) : "")
moveLine(fvg.fvgSeperator, volumeBarsLeftSide ? startX : maxEndX, (fvg.min + fvg.max) / 2, volumeBarsLeftSide ? maxEndX : startX)
line.set_xy1(fvg.fvgTextSeperator, volumeBarsLeftSide ? maxEndX : startX, fvg.max)
line.set_xy2(fvg.fvgTextSeperator, volumeBarsLeftSide ? maxEndX : startX, fvg.min)
areaOfFVG (FVG fvg) =>
XA1 = fvg.startBarIndex
XA2 = na(fvg.endBarIndex) ? fvg.startBarIndex + extendZonesBy : fvg.endBarIndex
YA1 = fvg.max
YA2 = fvg.min
edge1 = math.sqrt((XA2 - XA1) * (XA2 - XA1) + (YA2 - YA2) * (YA2 - YA2))
edge2 = math.sqrt((XA2 - XA2) * (XA2 - XA2) + (YA2 - YA1) * (YA2 - YA1))
totalArea = edge1 * edge2
totalArea
doFVGsTouch (FVG fvg1, FVG fvg2) =>
XA1 = fvg1.startBarIndex
XA2 = na(fvg1.endBarIndex) ? fvg1.startBarIndex + extendZonesBy : fvg1.endBarIndex
YA1 = fvg1.max
YA2 = fvg1.min
XB1 = fvg2.startBarIndex
XB2 = na(fvg2.endBarIndex) ? fvg2.startBarIndex + extendZonesBy : fvg2.endBarIndex
YB1 = fvg2.max
YB2 = fvg2.min
intersectionArea = math.max(0, math.min(XA2, XB2) - math.max(XA1, XB1)) * math.max(0, math.min(YA1, YB1) - math.max(YA2, YB2))
unionArea = areaOfFVG(fvg1) + areaOfFVG(fvg2) - intersectionArea
float overlapPercentage = (intersectionArea / unionArea) * 100.0
if intersectionArea > 0.0
//log.info(str.tostring(intersectionArea) + " | " + str.tostring(unionArea))
log.info(str.tostring(overlapPercentage))
if overlapPercentage >= overlapThresholdPercentage
true
else
false
findFVG () =>
bearCondition = false
bullCondition = false
threshold = ta.cum((high - low) / low) / bar_index
shortTerm = ta.sma(volume, 5)
longTerm = ta.sma(volume, 15)
averageBarSize = ta.sma(math.abs(open - close), 20)
firstBarSize = math.abs(open - close)
secondBarSize = math.abs(open[1] - close[1])
thirdBarSize = math.abs(open[2] - close[2])
barSizeCheck = true
if secondBarSize < (firstBarSize + thirdBarSize) * fvgSensitivity or secondBarSize < averageBarSize * fvgSensitivity
barSizeCheck := false
fvgBarsCheck = false
if fvgBars == "Same Type"
if (open > close and open[1] > close[1] and open[2] > close[2]) or (open <= close and open[1] <= close[1] and open[2] <= close[2])
fvgBarsCheck := true
else
fvgBarsCheck := true
if fvgBarsCheck and barSizeCheck
if fvgFilterMethod == "Average Range"
bearCondition := (low[2] - high) / high > threshold
bullCondition := (low - high[2]) / high[2] > threshold
else if fvgFilterMethod == "Volume Threshold"
thresholdMultiplier = 1.0 + (volumeThresholdPercent / 100.0)
bearCondition := shortTerm > longTerm * thresholdMultiplier and (low[2] - high) / high > threshold
bullCondition := shortTerm > longTerm * thresholdMultiplier and (low - high[2]) / high[2] > threshold
bearFVG = high < low[2] and close[1] < low[2] and bearCondition
bullFVG = low > high[2] and close[1] > high[2] and bullCondition
float totalVolume = math.sum(volume, 3)
FVG newFVG = bearFVG ? createFVG(low[2], high, false, time, totalVolume) : bullFVG ? createFVG(low, high[2], true, time, totalVolume) : na
if not na(newFVG)
newFVG.startBarIndex := bar_index - (startZoneFrom == "First Bar" ? 2 : 0)
if bearFVG
newFVG.lowVolume := volume + volume[1]
newFVG.highVolume := volume[2]
else
newFVG.lowVolume := volume[2]
newFVG.highVolume := volume + volume[1]
[bullFVG, bearFVG, newFVG]
isFVGValid (FVG fvg) =>
valid = true
if not showInvalidated and not na(fvg.endBarIndex)
valid := false
else if bar_index - fvg.startBarIndex > maxFVGDistanceToCurrent
valid := false
else if fvg.disabled
valid := false
else if not na(fvg.endBarIndex) and (fvg.endBarIndex - fvg.startBarIndex) < minimumFVGSize
valid := false
else if na(fvg.endBarIndex) and deleteUntouched and (bar_index - fvg.startBarIndex) > deleteUntouchedAfterXBars
valid := false
valid
combineFVGsFunc () =>
if fvgList.size() > 0
lastCombinations = 999
while lastCombinations > 0
lastCombinations := 0
for i = 0 to fvgList.size() - 1
curFVG1 = fvgList.get(i)
for j = 0 to fvgList.size() - 1
curFVG2 = fvgList.get(j)
if i == j
continue
if not isFVGValid(curFVG1) or not isFVGValid(curFVG2)
continue
if curFVG1.isBull != curFVG2.isBull
continue
if doFVGsTouch(curFVG1, curFVG2)
curFVG1.disabled := true
curFVG2.disabled := true
FVG newFVG = createFVG(math.max(curFVG1.max, curFVG2.max), math.min(curFVG1.min, curFVG2.min), curFVG1.isBull, math.min(curFVG1.t, curFVG2.t), 0)
newFVG.startBarIndex := math.min(curFVG1.startBarIndex, curFVG2.startBarIndex)
newFVG.endBarIndex := math.max(nz(curFVG1.endBarIndex), nz(curFVG2.endBarIndex))
newFVG.endBarIndex := newFVG.endBarIndex == 0 ? na : newFVG.endBarIndex
newFVG.totalVolume := curFVG1.totalVolume + curFVG2.totalVolume
newFVG.lowVolume := curFVG1.lowVolume + curFVG2.lowVolume
newFVG.highVolume := curFVG2.highVolume + curFVG2.highVolume
newFVG.combined := true
fvgList.unshift(newFVG)
lastCombinations += 1
// Add Found FVG
[isBull, isBear, foundFVG] = findFVG()
if not na(foundFVG)
fvgList.unshift(foundFVG)
while fvgList.size() > showLastXFVGs
fvgList.pop()
// Find Closed FVGs
if fvgList.size () > 0
for i = 0 to fvgList.size() - 1
curFVG = fvgList.get(i)
if not na(curFVG.endBarIndex)
continue
if curFVG.isBull and (fvgEndMethod == "Wick" ? low < curFVG.min : close < curFVG.min)
curFVG.endBarIndex := bar_index
if not curFVG.isBull and (fvgEndMethod == "Wick" ? high > curFVG.max : close > curFVG.max)
curFVG.endBarIndex := bar_index
// Remove Old FVGs
FVGstoRemove = array.new<int>(0)
if fvgList.size() > 0
for i = 0 to fvgList.size() - 1
curIndex = fvgList.size() - 1 - i
curFVG = fvgList.get(curIndex)
if not isFVGValid(curFVG)
FVGstoRemove.push(curIndex)
safeDeleteFVG(curFVG)
if FVGstoRemove.size () > 0
for i = 0 to FVGstoRemove.size() - 1
deleteIndex = FVGstoRemove.get(i)
fvgList.remove(deleteIndex)
if barstate.islast
log.info("Found " + str.tostring(fvgList.size()) + " FVGs.")
// Combine FVGs
if combineFVGs
combineFVGsFunc()
// Render FVGs
extendedLastXFVGsCount = 0
if fvgList.size() > 0
for i = 0 to fvgList.size() - 1
curFVG = fvgList.get(i)
if not isFVGValid (curFVG)
continue
if extendLastFVGs and na(curFVG.endBarIndex) and extendedLastXFVGsCount < extendLastXFVGsCount
extendedLastXFVGsCount += 1
curFVG.extendInfinite := true
if curFVG.isRendered
safeDeleteFVG(curFVG)
renderFVG(curFVG)
//plotchar(fvgList.size(), "FVG Count", na, location.top)