Volumized Inverse Fair Value Gaps | Flux Charts

fluxchart · study · 391 行 · 点赞 3,026 · TradingView 原页

本页源码来自 TradingView 公开发布的开源脚本,版权归原作者所有, 请遵循其原始许可(Pine 脚本常见 CC BY-NC-SA / MPL-2.0 / MIT)。 本项目仅用于研究检索与许可范围内的移植。

Pine Script

// 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)

← 返回列表