本页源码来自 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/
// © fluxchart
//@version=5
const bool DEBUG = false
const int maxBoxesCount = 500
const int showLastXFVGs = 10
const int extendLastXFVGsCount = 3
const int minimumFVGSize = 2
const int minimumIFVGSize = 3
const float overlapThresholdPercentage = 0.01
const int maxFVGDistanceToCurrent = 4000
const bool ifvgFull = true
indicator(title = 'Inversion Fair Value Gaps | Flux Charts',shorttitle = 'IFVG | Flux Charts', overlay = true, max_boxes_count = maxBoxesCount, max_labels_count = maxBoxesCount, max_lines_count = maxBoxesCount, max_bars_back = 5000)
volCheck = (ta.cum(volume) > 0)
//if not volCheck
//runtime.error("No volume info available.")
fvgEndMethod = input.string("Wick", "FVG Zone Invalidation", options = ["Wick", "Close"], group = "General Configration")
ifvgEndMethod = input.string("Wick", "IFVG Zone Invalidation", options = ["Wick", "Close"], group = "General Configration")
fvgFilterMethod = input.string("Average Range", "Zone Filtering", options = ["Average Range", "Volume Threshold"], group = "General Configration")
volumeThresholdPercent = input.int(30, "Volume Threshold %", group = "General Configration", 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 Configration")
fvgSensitivityText = input.string("Normal", "Detection Sensitivity", options = ["High", "Normal", "Low"], group = "General Configration")
combineFVGs = DEBUG ? input.bool(true, "Combine Zones", group = "General Configration") : true
deleteUntouched = DEBUG ? input.bool(true, "", group = "General Configration", inline = "deleteUntouched") : true
deleteUntouchedAfterXBars = DEBUG ? input.int(200, "Delete Untouched Zones After", group = "General Configration", minval = 5, maxval = 500, inline = "deleteUntouched") : 200
showInvalidated = input.bool(true, "Show Historic Zones", group = "General Configration")
bullColor = DEBUG ? input.color(#08998180, "Bull Color", group = "Style", inline = "BBcolors") : #08998180
bearColor = DEBUG ? input.color(#f2364580, "Bear Color", group = "Style", inline = "BBcolors") : #f2364580
bullishInverseColor = input(color.new(#2962ff, 75), "Bullish IFVG", inline = 'breakerColor', group = 'Style', display = display.none)
bearishInverseColor = input(color.new(#ffeb3b, 75), "Bearish IFVG", inline = 'breakerColor', group = 'Style', display = display.none)
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 = DEBUG ? input.string("Last Bar", "Start Zones From", options = ["First Bar", "Last Bar"], group = "Style") : "Last Bar"
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 isInverse = false
int inverseEndIndex = na
bool extendInfinite = false
bool combined = false
bool disabled = false
bool isRendered = false
float lowVolume = na
float highVolume = na
float inverseVolume = na
box fvgBox = na
box fvgBoxText = na
box fvgBoxPositive = na
box fvgBoxNegative = na
box ifvgBox = na
line fvgSeperator = na
line fvgTextSeperator = na
line inverseLineTop = na
line inverseLineBottom = 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)
box.delete(fvg.ifvgBox)
line.delete(fvg.fvgSeperator)
line.delete(fvg.fvgTextSeperator)
line.delete(fvg.inverseLineTop)
line.delete(fvg.inverseLineBottom)
var FVG[] fvgList = array.new<FVG>()
var int totalFVGIndex = 0
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
fvg.isRendered := true
if not ifvgFull
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 = nz(int((math.min(fvg.highVolume, fvg.lowVolume) / math.max(fvg.highVolume, fvg.lowVolume)) * 100.0))
fvgText = fvg.totalVolume == 0 ? "FVG" : str.tostring(fvg.totalVolume, format.volume) + " (" + str.tostring(percentage) + "%)" + (combinedText and fvg.combined ? "\n[Combined]" : "")
box.set_text(fvg.fvgBoxText, fvgText)
if fvg.combined and not changeCombinedFVGsColor
fvg.fvgBox.set_bgcolor(colorWithTransparency(fvg.isBull ? bullColor : bearColor, 1.1))
showHighLowBoxText = false
if fvg.totalVolume != 0
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)
// IFVG
if fvg.isInverse
inverseColor = fvg.isBull ? bearishInverseColor : bullishInverseColor
fvg.ifvgBox := createFVGBox(inverseColor)
startIndexIFVG = ifvgFull ? fvg.startBarIndex : fvg.endBarIndex
inverseZoneSize = na(fvg.inverseEndIndex) ? (last_bar_index - startIndexIFVG) : (fvg.inverseEndIndex - startIndexIFVG)
moveBox(fvg.ifvgBox, startIndexIFVG, fvg.max, startIndexIFVG + inverseZoneSize, fvg.min)
box.set_text(fvg.ifvgBox, (true ? str.tostring(fvg.inverseVolume, format.volume) + "\n" : "") + (combinedText and fvg.combined ? "[Combined]\n" : "" + "IFVG"))
fvg.inverseLineTop := line.new(startIndexIFVG, fvg.max, startIndexIFVG + inverseZoneSize, fvg.max, extend = extend.none, color = colorWithTransparency(inverseColor, 0), style = line.style_dashed)
fvg.inverseLineBottom := line.new(startIndexIFVG, fvg.min, startIndexIFVG + inverseZoneSize, fvg.min, extend = extend.none, color = colorWithTransparency(inverseColor, 0), style = line.style_dashed)
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 = volCheck ? ta.sma(volume, 5) : 1
longTerm = volCheck ? ta.sma(volume, 15) : 0
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 = volCheck ? math.sum(volume, 3) : 0
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 := volCheck ? (volume + volume[1]) : 0
newFVG.highVolume := volCheck ? volume[2] : 0
else
newFVG.lowVolume := volCheck ? volume[2] : 0
newFVG.highVolume := volCheck ? (volume + volume[1]) : 0
[bullFVG, bearFVG, newFVG]
isFVGValid (FVG fvg) =>
valid = true
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
isIFVGValid (FVG fvg) =>
valid = true
if (not showInvalidated) and (not na(fvg.inverseEndIndex))
valid := false
else if not na(fvg.inverseEndIndex) and (fvg.inverseEndIndex - fvg.endBarIndex) < minimumIFVGSize
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)
// Combine FVG
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 := curFVG1.highVolume + curFVG2.highVolume
// Combine IFVG
newFVG.inverseEndIndex := math.max(nz(curFVG1.inverseEndIndex), nz(curFVG2.inverseEndIndex))
newFVG.inverseEndIndex := newFVG.inverseEndIndex == 0 ? na : newFVG.inverseEndIndex
if curFVG1.isInverse or curFVG2.isInverse
newFVG.inverseVolume := nz(curFVG1.inverseVolume) + nz(curFVG2.inverseVolume)
newFVG.isInverse := true
newFVG.combined := true
fvgList.unshift(newFVG)
lastCombinations += 1
//if barstate.islast and barstate.isnew and totalFVGIndex > totalFVGIndex[2]
// log.info("NEW FVG!")
alertcondition(barstate.islast and barstate.isnew and totalFVGIndex > totalFVGIndex[2], "New FVG @ {{ticker}}", "")
// Add Found FVG
[isBull, isBear, foundFVG] = findFVG()
if not na(foundFVG)
totalFVGIndex += 1
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)
// IFVG Close
if curFVG.isInverse and na(curFVG.inverseEndIndex)
if (not curFVG.isBull) and (ifvgEndMethod == "Wick" ? low < curFVG.min : close < curFVG.min)
curFVG.inverseEndIndex := bar_index
if curFVG.isBull and (ifvgEndMethod == "Wick" ? high > curFVG.max : close > curFVG.max)
curFVG.inverseEndIndex := bar_index
// FVG Close
if na(curFVG.endBarIndex)
if curFVG.isBull and (fvgEndMethod == "Wick" ? low < curFVG.min : close < curFVG.min)
curFVG.endBarIndex := bar_index
curFVG.isInverse := true
curFVG.inverseVolume := nz(volume)
if (not curFVG.isBull) and (fvgEndMethod == "Wick" ? high > curFVG.max : close > curFVG.max)
curFVG.endBarIndex := bar_index
curFVG.isInverse := true
curFVG.inverseVolume := nz(volume)
// 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 isIFVGValid(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)