Volume Bubbles & Liquidity Heatmap [LuxAlgo]

LuxAlgo · study · 322 行 · 点赞 5,262 · TradingView 原页

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

Pine Script

// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo
 
//@version=6
indicator('Volume Bubbles & Liquidity Heatmap [LuxAlgo]','LuxAlgo - Volume Bubbles & Liquidity Heatmap', overlay = true, max_polylines_count = 100, max_labels_count = 100, max_lines_count = 101, max_boxes_count = 500, max_bars_back = 5000)
//---------------------------------------------------------------------------------------------------------------------}
//CONSTANTS & STRINGS & INPUTS
//---------------------------------------------------------------------------------------------------------------------{
GREEN               = #089981
RED                 = #F23645
 
BUYSELL             = 'Buy & Sell Volume'
DELTA               = 'Delta Volume'
TOTAL               = 'Total Volume'
 
TOP                 = 'Top'
BOTTOM              = 'Bottom'
MIDDLE              = 'Middle'
 
BUBBLES_GROUP       = 'BUBBLES'
LABELS_GROUP        = 'LABELS'
SEPARATOR_GROUP     = 'SEPARATORS'
HISTOGRAM_GROUP     = 'LIQUIDITY HEATMAP'
STYLE_GROUP         = 'STYLE'
 
bubblesModeTooltip  = 'Select from the following options: total volume, buy and sell volume, or the delta between buy and sell volume.'
bubbleTFTooltip     = 'Select the timeframe for which the bubbles will be displayed.'
bubbleSizeTooltip   = 'Select the size of the bubbles as a percentage.'
priceRatioTooltip   = 'Select the shape of the bubbles. The larger the number, the more vertical the bubbles will be stretched.'
 
lookbackPeriodInput = input.int(    2000,       'Execute on last X bars',   minval = 100, maxval = 5000)
 
bubblesInput        = input.bool(   true,       'Display Bubbles',  group = BUBBLES_GROUP)
bubblesModeInput    = input.string( BUYSELL,    'Bubble Mode',      group = BUBBLES_GROUP,  tooltip = bubblesModeTooltip,   options = [TOTAL,BUYSELL,DELTA])
bubbleTimeframeInput= input.timeframe('1D',     'Bubble Timeframe', group = BUBBLES_GROUP,  tooltip = bubbleTFTooltip)
bubbleSizeInput     = input.int(    100,        'Bubble Size %',    group = BUBBLES_GROUP,  tooltip = bubbleSizeTooltip,    minval = 10,    maxval = 100) * 0.01
priceRatioInput     = input.float(  0.5,        'Bubble Shape',     group = BUBBLES_GROUP,  tooltip = priceRatioTooltip,    minval = 0,     step = 0.1)
 
labelInput          = input.bool(   true,       'Display Labels',   group = LABELS_GROUP,   inline = 'labels')
labelSizeInput      = input.int(    14,         '',                 group = LABELS_GROUP,   inline = 'labels')
labelLocationInput  = input.string( BOTTOM,     '',                 group = LABELS_GROUP,   inline = 'labels', options = [TOP,MIDDLE,BOTTOM])
 
separatorInput      = input.bool(   true,                           'Display Separators',   group = SEPARATOR_GROUP, inline = 'separators')
separatorColorInput = input.color(  color.new(color.silver,50),   '',                     group = SEPARATOR_GROUP, inline = 'separators')
 
histogramInput      = input.bool(   true,                           'Display Heatmap',      group = HISTOGRAM_GROUP)
histogramSizeInput  = input.int(    25,                             'Heatmap Rows',         group = HISTOGRAM_GROUP, minval = 2, maxval = 500)
histogramCellInput  = input.int(    20,                             'Cell Minimum Size',    group = HISTOGRAM_GROUP, minval = 2)
histogramColor1Input= input.color(  color.new(RED,90),              '',                     group = HISTOGRAM_GROUP, inline = 'histogram')
histogramColor2Input= input.color(  color.new(color.yellow,90),   '',                     group = HISTOGRAM_GROUP, inline = 'histogram')
histogramColor3Input= input.color(  color.new(color.blue,90),     '',                     group = HISTOGRAM_GROUP, inline = 'histogram')
 
buyColorInput       = input.color(  color.new(GREEN,50),            '',                     group = STYLE_GROUP,    inline = 'colors')
sellColorInput      = input.color(  color.new(RED,50),              '',                     group = STYLE_GROUP,    inline = 'colors')
 
//---------------------------------------------------------------------------------------------------------------------}
//DATA STRUCTURES & VARIABLES
//---------------------------------------------------------------------------------------------------------------------{
type data
    float buyVolume
    float sellVolume
    float highPrice
    float lowPrice
    int barIndex
    bool newBubble
 
type bubble
    array<float> buyVolume
    array<float> sellVolume
    array<float> highs
    array<float> lows
    array<int> bars
 
type histogramBox
    int firstBar
    int lastBar
    float liquidity
    string extend
 
type histogramLevel
    float top
    float bottom
    array<histogramBox> boxes
 
type histogramData
    int barIndex
    float barVolume
    float barPrice
 
type fullHistogram
    float top
    float bottom
    float rowSize
    float maxVolume
    array<histogramLevel> levels
    array<histogramData> bars
 
var fullHistogram histogram = fullHistogram.new(0,0,0,0,array.new<histogramLevel>(),array.new<histogramData>())
var array<data> bars        = array.new<data>()
var array<bubble> bubbles   = array.new<bubble>()   
var array<float> volumes    = array.new<float>()
var array<float> ranges     = array.new<float>()
var array<float> deltas     = array.new<float>()
 
//---------------------------------------------------------------------------------------------------------------------}
//USER-DEFINED FUNCTIONS
//---------------------------------------------------------------------------------------------------------------------{
fetchData() =>
    float barTop        = high - math.max(open,close)
    float barBottom     = math.min(open,close) - low
    float barRange      = high - low
    bool bullBar        = close - open > 0
    
    float buyRange      = bullBar ? barRange : barTop + barBottom
    float sellRange     = bullBar ? barTop + barBottom : barRange
    float totalRange    = barRange + barTop + barBottom
 
    float buyRatio      = buyRange / totalRange
    float sellRatio     = sellRange / totalRange
 
    float buyVolume     = math.round(buyRatio * volume)
    float sellVolume    = math.round(sellRatio * volume)
 
    bool newBubble      = timeframe.change(bubbleTimeframeInput)
    
    if bars.size() >= lookbackPeriodInput
        bars.shift()
        histogram.bars.shift()
 
    bars.push(data.new(buyVolume,sellVolume,high,low,bar_index,newBubble))    
    histogram.bars.push(histogramData.new(bar_index,volume,math.round_to_mintick(hlc3)))
 
    if histogram.top == 0
        histogram.top       := high
        histogram.bottom    := low
    else
        histogram.top       := math.max(histogram.top,high)
        histogram.bottom    := math.min(histogram.bottom,low)
    
fetchBubbles() =>
    bubbles.clear()
    volumes.clear()
    ranges.clear()
    deltas.clear()
    for [index, eachBar] in bars        
        if eachBar.newBubble
            bubbles.push(bubble.new(array.new<float>(),array.new<float>(),array.new<float>(),array.new<float>(),array.new<int>()))
            volumes.push(0)
            ranges.push(0)
            deltas.push(0)
 
        if bubbles.size() != 0
            bubble currentBubble = bubbles.last()        
            currentBubble.highs.push(eachBar.highPrice)
            currentBubble.lows.push(eachBar.lowPrice)
            currentBubble.bars.push(eachBar.barIndex)
            currentBubble.buyVolume.push(eachBar.buyVolume)
            currentBubble.sellVolume.push(eachBar.sellVolume)
 
            volumes.set(-1, bubbles.last().buyVolume.sum() + bubbles.last().sellVolume.sum())
            ranges.set( -1, bubbles.last().highs.max() - bubbles.last().lows.min())
            deltas.set( -1, bubbles.last().buyVolume.sum() - bubbles.last().sellVolume.sum())
 
coordinates(int anchorBar, float anchorPrice, float angle, float radiusBar, float radiusPrice) =>
    x = math.min(math.round(anchorBar + radiusBar * math.cos(math.toradians(angle))),last_bar_index + 500)
    y = anchorPrice + radiusPrice * math.sin(math.toradians(angle))
    [x,y]
 
drawLabel(string switchData, float bubbleVolume, color bubbleColor,int anchorBar, float anchorPrice, float angle, float radiusBar, float radiusPrice) =>
    [labelPoint, labelStyle] = switch switchData
        TOP =>
            [x, y] = coordinates(anchorBar, anchorPrice, angle, radiusBar, radiusPrice)
            [chart.point.new(na,x,y),label.style_label_down]
        MIDDLE => 
            [x, y] = coordinates(anchorBar, anchorPrice, angle, radiusBar, radiusPrice)
            [chart.point.new(na,x,y),label.style_none]
        BOTTOM =>
            [x, y] = coordinates(anchorBar, anchorPrice, angle, radiusBar, radiusPrice)
            [chart.point.new(na,x,y),label.style_label_up]
 
    label.new(labelPoint,str.tostring(bubbleVolume,format.volume), textcolor = color.new(bubbleColor,0), color = color(na), style = labelStyle, size = labelSizeInput)
 
drawFullBubble(bubble eachBubble, bool deltaBubble) =>
    array<chart.point> bubblePoints = array.new<chart.point>()
    int anchorBar                   = eachBubble.bars.median()
    float anchorPrice               = math.round_to_mintick(0.5 * (eachBubble.highs.max() + eachBubble.lows.min()))
    
    float bubbleVolume              = math.abs(eachBubble.buyVolume.sum() + (deltaBubble ? -1 : 1 ) * eachBubble.sellVolume.sum())
    float bubbleRatio               = (bubbleVolume / (deltaBubble ? deltas.max() : volumes.max())) * bubbleSizeInput
    float radiusBar                 = math.floor(0.5 * eachBubble.bars.size()) * bubbleRatio
    float range2barRatio            = ranges.max() / eachBubble.bars.size()
    float radiusPrice               = radiusBar * range2barRatio * priceRatioInput        
 
    for deltaIndex = 0 to 360
        [x, y] = coordinates(anchorBar, anchorPrice, deltaIndex, radiusBar, radiusPrice)            
        bubblePoints.push(chart.point.new(na,x,y))
    
    float bubbleDelta   = eachBubble.buyVolume.sum() - eachBubble.sellVolume.sum()                
    bubbleColor         = bubbleDelta > 0 ? buyColorInput : sellColorInput   
 
    if labelInput
        int labelAngle = labelLocationInput == TOP ? 90 : labelLocationInput == BOTTOM ? 270 : 0
        drawLabel(labelLocationInput, bubbleVolume, bubbleColor, anchorBar, anchorPrice, labelAngle, labelLocationInput != MIDDLE ? radiusBar : 0, labelLocationInput != MIDDLE ? radiusPrice : 0)
 
    if separatorInput
        line.new(chart.point.new(na,eachBubble.bars.last(),eachBubble.highs.max()),chart.point.new(na,eachBubble.bars.last(),eachBubble.lows.min()),color = separatorColorInput, style = line.style_dashed)
 
    polyline.new(bubblePoints, false, true, line_color = color.new(bubbleColor,0), fill_color = bubbleColor)  
 
drawSplitBubble(bubble eachBubble) =>
    array<chart.point> buyVolumePoints  = array.new<chart.point>()
    array<chart.point> sellVolumePoints = array.new<chart.point>()
 
    int anchorBar                       = eachBubble.bars.median()        
    float anchorPrice                   = math.round_to_mintick(0.5 * (eachBubble.highs.max() + eachBubble.lows.min()))
    float bubbleVolume                  = eachBubble.buyVolume.sum() + eachBubble.sellVolume.sum()        
    float bubbleRatio                   = (bubbleVolume / volumes.max()) * bubbleSizeInput
    float radiusBar                     = math.floor(0.5 * eachBubble.bars.size()) * bubbleRatio
    float range2barRatio                = ranges.max() / eachBubble.bars.size()
    float radiusPrice                   = radiusBar * range2barRatio * priceRatioInput
    float volumeRatio                   = bubbleVolume / 360
    float normalizedBuyVolume           = eachBubble.buyVolume.sum() / volumeRatio
    float normalizedSellVolume          = eachBubble.sellVolume.sum() / volumeRatio
 
    buyVolumePoints.push(chart.point.new(na,anchorBar,anchorPrice))
    for buyIndex = 0 to normalizedBuyVolume            
        [x, y] = coordinates(anchorBar, anchorPrice, buyIndex, radiusBar, radiusPrice)            
        buyVolumePoints.push(chart.point.new(na,x,y))
    buyVolumePoints.push(chart.point.new(na,anchorBar,anchorPrice))
 
    sellVolumePoints.push(chart.point.new(na,anchorBar,anchorPrice))
    for sellIndex = normalizedBuyVolume to normalizedBuyVolume + normalizedSellVolume
        [x, y] = coordinates(anchorBar, anchorPrice, sellIndex, radiusBar, radiusPrice)            
        sellVolumePoints.push(chart.point.new(na,x,y))
    sellVolumePoints.push(chart.point.new(na,anchorBar,anchorPrice))
 
    if labelInput
        string buyLabelLocation     = labelLocationInput == MIDDLE ? MIDDLE : TOP
        string sellLabelLocation    = labelLocationInput == MIDDLE ? MIDDLE : BOTTOM 
        float labelRadius           = labelLocationInput == MIDDLE ? 0.5*radiusPrice : radiusPrice
 
        drawLabel(buyLabelLocation,eachBubble.buyVolume.sum(),color.new(buyColorInput,0),anchorBar, anchorPrice, 90, radiusBar, labelRadius)           
        drawLabel(sellLabelLocation,eachBubble.sellVolume.sum(),color.new(sellColorInput,0),anchorBar, anchorPrice, 270, radiusBar, labelRadius)    
 
    if separatorInput
        line.new(chart.point.new(na,eachBubble.bars.last(),eachBubble.highs.max()),chart.point.new(na,eachBubble.bars.last(),eachBubble.lows.min()),color = separatorColorInput, style = line.style_dashed)
 
    polyline.new(buyVolumePoints, false, true, line_color = color.new(buyColorInput,0), fill_color = buyColorInput)
    polyline.new(sellVolumePoints,false, true, line_color = color.new(sellColorInput,0),fill_color = sellColorInput)
 
drawBubbles() =>
    for eachPolyline in polyline.all
        eachPolyline.delete()
    for eachLabel in label.all
        eachLabel.delete()
    for eachLine in line.all
        eachLine.delete()
    for eachBubble in bubbles
        switch bubblesModeInput
            TOTAL   => drawFullBubble(eachBubble,false)
            BUYSELL => drawSplitBubble(eachBubble)
            DELTA   => drawFullBubble(eachBubble,true)
 
drawHistogram() =>
    for eachBox in box.all
        eachBox.delete()
    
    histogram.rowSize := math.round_to_mintick((histogram.top - histogram.bottom)/histogramSizeInput)        
    histogram.levels.clear()
    for index = 0 to histogramSizeInput - 1
        float bottom    = histogram.bottom + index * histogram.rowSize
        float top       = bottom + histogram.rowSize    
        histogram.levels.push(histogramLevel.new(top,bottom,array.new<histogramBox>()))
 
    int lastLevel = 0
    for [index,eachBar] in histogram.bars        
        int level                       = math.floor((eachBar.barPrice - histogram.bottom) / histogram.rowSize)    
        level                           := math.min(level,histogram.levels.size() - 1)
        histogramLevel h_istogramLevel  = histogram.levels.get(level)
        array<histogramBox> boxes       = h_istogramLevel.boxes
 
        if boxes.size() == 0
            boxes.push(histogramBox.new(eachBar.barIndex,eachBar.barIndex,eachBar.barVolume,extend.right))      
        else
            histogramBox b_ox   = boxes.last()
            b_ox.lastBar        := eachBar.barIndex
 
            if level == lastLevel                                
                b_ox.liquidity += eachBar.barVolume                                
            else
                bool boxIsBigEnough = b_ox.lastBar - b_ox.firstBar >= histogramCellInput                
                if boxIsBigEnough
                    b_ox.extend := extend.none                                                
                    boxes.push(histogramBox.new(eachBar.barIndex,eachBar.barIndex,eachBar.barVolume,extend.right))
                else
                    b_ox.liquidity += eachBar.barVolume
            histogram.maxVolume := math.max(histogram.maxVolume,b_ox.liquidity)
        lastLevel := level
 
    for [index,eachLevel] in histogram.levels
        for eachBox in eachLevel.boxes
            float liquidityRatio    = eachBox.liquidity / histogram.maxVolume
            color backgroundColor   = color.from_gradient(liquidityRatio,liquidityRatio <= 0.5 ? 0.0 : 0.5,liquidityRatio <= 0.5 ? 0.5 : 1.0,liquidityRatio <= 0.5 ? histogramColor3Input : histogramColor2Input,liquidityRatio <= 0.5 ? histogramColor2Input : histogramColor1Input)
            box.new(chart.point.new(na,eachBox.firstBar,eachLevel.top),chart.point.new(na,eachBox.lastBar,eachLevel.bottom),extend = eachBox.extend, border_color = color(na),bgcolor = backgroundColor)                       
 
//---------------------------------------------------------------------------------------------------------------------}
//MUTABLE VARIABLES & EXECUTION
//---------------------------------------------------------------------------------------------------------------------{
executionWindow = bar_index >= last_bar_index - lookbackPeriodInput
 
if executionWindow
    fetchData()
 
    if barstate.islastconfirmedhistory or (barstate.isrealtime and barstate.isconfirmed)    
        if histogramInput
            drawHistogram()
 
        if bubblesInput
            fetchBubbles()
            drawBubbles()
        
//---------------------------------------------------------------------------------------------------------------------}

← 返回列表