Indicator · Pine Script v5 · On the chart

Structure TA

by trananh211

A market structure overlay in the smart money style.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code1,348
Settings40inputs
LicenceNone statedfile: MPL-2.0
Last update6 months ago9 Apr 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

1,348 lines of Pine Script v5 from SMC3.pine, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

SMC3.pine
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// Big thanks to AlbaTherium for orginal script https://www.tradingview.com/script/nSSzkNji-AlbaTherium-Structure-mapping-v2-0/
// Author: Ma Bang Chu from https://t.me/wyckoffneverdie


//@version=5
indicator("Structure TA", "ICT TA", true, max_bars_back = 5000, max_labels_count = 500, max_lines_count = 500)

//drawing options
DefaultStructSetting = "Structure Settings"
groupAsianSessionRange2 = "Settings"
show_session = input(defval=false, title='Show Sessions', group=groupAsianSessionRange2, inline="session1")
bias_reason = input.bool(false, "Show Bias Reasoning", tooltip = "Giải thích lý do tại sao áp dụng Daily bias", group = groupAsianSessionRange2)

//Seasonal Adjustment Input
season = input.string(defval='Winter/Spring', options=['Winter/Spring', 'Summer/Fall'], title='US Season', group=groupAsianSessionRange2, inline="session1")

structure_type = input.string ("Choch with IDM",title='Structure type', group=DefaultStructSetting, options=["Choch without IDM", "Choch with IDM"])
showHL = input.bool(false, "Mark H/L", inline = "HL_Struct", group = DefaultStructSetting)
colorHL = input.color(color.rgb(0, 0, 0, 8), "High/Low", group = DefaultStructSetting, inline = "HL_Struct")
showVolMajor = input.bool(true, "Show Volume on Major HL", group = DefaultStructSetting)

showCircleHL = input.bool(true, "Mark Circle", inline = "HL_Circle", group = DefaultStructSetting)
bull = input.color(color.rgb(76, 175, 79, 8), "Bullish", inline = "HL_Circle", group = DefaultStructSetting)
bear = input.color(color.rgb(255, 82, 82, 8)  , "Bearish", inline = "HL_Circle", group = DefaultStructSetting)

showMn = input.bool(true, "Show Internal Structure", inline = "internalStructure" , group = DefaultStructSetting)
internalStructureType = input.string("auto", title="Size",
     options=["auto", "tiny", "small", "default", "large", "very large"], group = DefaultStructSetting, inline = 'internalStructure')
size_internalStructure = switch internalStructureType
    "tiny" => size.tiny
    "small"      => size.small
    "default"    => size.normal
    "large"      => size.large
    "very large" => size.huge
    "auto" => size.auto
puUpco = input.color(#f57f17  , "High pivots", inline = "Line3", group = DefaultStructSetting)
puDnco = input.color(#006064, "Low pivots" , inline = "Line3", group = DefaultStructSetting)
showVolGann = input.bool(true, "Show Volume on Gann waves", group = DefaultStructSetting)

showBOS = input.bool(true, "Show B O S", group = DefaultStructSetting, inline = "sShowLine")
showChoChStructure = input.bool(true, "Show CHoCH", group = DefaultStructSetting, inline = "sShowLine")
showIDM = input.bool(true,"Show IDM", group = DefaultStructSetting, inline = "sShowLine")

showPdh = input.bool(true,"Show PDH",  group = DefaultStructSetting, inline = "sShowLinePDH")
lengPdh = input.int(19, title="",  group=DefaultStructSetting, inline = "sShowLinePDH")

showPdl = input.bool(true,"Show PDL", group = DefaultStructSetting, inline = "sShowLinePDL")
lengPdl = input.int(19, title="", group=DefaultStructSetting, inline = "sShowLinePDL")

showMid = input.bool(false, "Show Equilibrium",  group = DefaultStructSetting, inline = "sShowLineEqu")
lengMid = input.int(19, title="", group=DefaultStructSetting, inline = "sShowLineEqu")

showSw = input.bool(true, "Show H/L sweeping lines",  group = DefaultStructSetting, inline = "sShowLinesweep")
markX = input.bool(true, 'Mark "X"',  group = DefaultStructSetting, inline = "sShowLinesweep")
colorSweep  = input.color(color.purple, "Sweeping Color", group = DefaultStructSetting, inline = "sShowLinesweep")

showTP = input.bool(true, 'Show Target profit', group = DefaultStructSetting, inline = "STarget")
colorTP = input.color(#b2b5be, 'Target Color', group = DefaultStructSetting, inline = "STarget")

showliveBOS = input.bool(true,"Show live BOS", inline = "liveB", group = DefaultStructSetting)
lengBos = input.int(11, title="", inline = "liveB", group=DefaultStructSetting)
showliveChoch = input.bool(true,"Show live ChoCh", inline = "liveCho", group = DefaultStructSetting)
lengChoch = input.int(11, title="", inline = "liveCho", group=DefaultStructSetting)
showliveIDM = input.bool(true,"Show live IDM", inline = "liveI", group = DefaultStructSetting)
lengIDM = input.int(11, title="", inline = "liveI", group=DefaultStructSetting)
colorIDM = input.color(color.rgb(68, 68, 68, 8), "IDM Color", inline = "liveI", group=DefaultStructSetting)

showSCOB = input.bool(true, "Show SCOB",  group = DefaultStructSetting, inline = "SCOB")
scobUp = input.color(color.aqua   , "Bullish SCOB", group = DefaultStructSetting, inline = "SCOB")
scobDn = input.color(color.fuchsia, "Bearish SCOB", group = DefaultStructSetting, inline = "SCOB")

showISB = input.bool(true, 'Show ISB',  group = DefaultStructSetting, inline = "ISB")
colorISB = input.color(#fbc02d,'Inside Bar', group = DefaultStructSetting, inline = "ISB")

showOSB = input.bool(true, 'Show OSB',  group = DefaultStructSetting, inline = "OSB")
colorOSB_up_Structure = input.color(#4caf50,'Bullish OSB', group = DefaultStructSetting, inline = "OSB")
colorOSB_down_Structure = input.color(#c12418,'Bearish OSB', group = DefaultStructSetting, inline = "OSB")

showPOI = input.bool(true, "Show POI", group="POI settings")
poi_type = input.string ("---",title='POI type', group="POI settings", options=["---", "Mother Bar"])
mergeRatio = input.float(defval=0, minval=0, maxval=0.5, step=0.02, title="Merge Ratio", group="POI settings" )
maxBarHistory = input.int(1000, title="Max IPA age", group="POI settings")
colorDemand = input.color(#d6e3ca63, 'Demand', group = "POI settings")
colorSupply = input.color(#ecaa9e63, 'Supply', group = "POI settings")
colorMitigated = input.color(#c0c0c033, 'Mitigated', group = "POI settings")

UltraVol = "Candle Name + Volume Vsa"
//Công thức//
lengthVolumeMA =        input.int(34,minval=1,title="Length of MA applied on Volume",group = UltraVol)
ratioUltraVolume =      input.float(1.25,minval=1,title="Ultra High Volume Ratio", group = UltraVol)
showLabelCandle = input.bool(false, "Show Label", group=UltraVol, inline = 'timing1')
candleUltraVolType = input.string("tiny", title="Size",
     options=["auto", "tiny", "small", "default", "large", "very large"], group = UltraVol, inline = 'timing1')
sizeCandleUltraVolume = switch candleUltraVolType
    "tiny" => size.tiny
    "small"      => size.small
    "default"    => size.normal
    "large"      => size.large
    "very large" => size.huge
    "auto" => size.auto

colorLabelCandle = input.color(#111111, 'Label Color', group=UltraVol, inline = 'timing1')
getVolume() => 
    float volumeMA  = 0
    volumeMA := nz(volumeMA[1]) + (volume-nz(volumeMA[1])) / lengthVolumeMA
    ultraHighVolumeMin   = volumeMA * ratioUltraVolume
    volUltraHigh        = volume >= ultraHighVolumeMin? true : false

//#region variable declaration
//Constant
const string IDM_TEXT = "IDM"
const string CHOCH_TEXT = "CHoCH" 
const string BOS_TEXT = "BOS"
const string PDH_TEXT = "PDH"
const string PDL_TEXT = "PDL"
const string MID_TEXT = "0.5"
const string ULTRAVOLUME = " has UltraVolume"

// Hàm tính tổng volume từ thời điểm t1 đến t2
f_get_range_volume(_t1, _t2) =>
    float _v = 0.0
    int i1 = -1
    int i2 = -1
    for i = 0 to 5000
        if time[i] == _t1
            i1 := i
        if time[i] == _t2
            i2 := i
        if i1 != -1 and i2 != -1
            break
    if i1 != -1 and i2 != -1
        int start = math.min(i1, i2)
        int end = math.max(i1, i2)
        for i = start to end
            _v += nz(volume[i])
    _v

//high low
var puHigh = high
var puLow = low
var L = low
var H = high
var idmLow = low
var idmHigh = high
var lastH = high
var lastL = low
var H_lastH = high
var L_lastHH = low
var H_lastLL = high
var L_lastL = low
var motherHigh = high[1]
var motherLow = low[1]

//bar indexes
var int motherBar = time[1]
var int puBar = na
var int puHBar = na
var int puLBar = na
var int idmLBar = na
var int idmHBar = na
var int HBar = time
var int LBar = time
var int lastHBar = time
var int lastLBar = time

//structure confirm
var bool mnStrc = na
var bool prevMnStrc = na
var bool isPrevBos = na
var bool findIDM = false
var bool isBosUp = false
var bool isBosDn = false
var bool isCocUp = true
var bool isCocDn = true

// Lưu trữ mốc thời gian của Đỉnh/Đáy nhỏ trước đó
var float lastInternalHighTime = na
var float lastInternalLowTime = na
// Mảng lưu label mũi tên Internal (để xóa sau)
var arrMnLabel = array.new_label(0)

//poi
var bool isSweepOBS = false
var int current_OBS = na
var float high_MOBS = na
var float low_MOBS = na

var bool isSweepOBD = false
var int current_OBD = na
var float low_MOBD = na
var float high_MOBD = na

//Array
var arrTopBotBar = array.new_int(1, time)
var arrTop = array.new_float(1, high)
var arrBot = array.new_float(1, low)

var arrPbHBar= array.new_int(0)
var arrPbHigh = array.new_float(0)
var arrPbLBar = array.new_int(0)
var arrPbLow = array.new_float(0)

var demandZone = array.new_box(0)
var supplyZone = array.new_box(0)
var mitigatedZoneSupply = array.new_string(0)
var mitigatedZoneDemand = array.new_string(0)

var arrIdmHigh = array.new_float(0) 
var arrIdmLow = array.new_float(0) 
var arrIdmHBar = array.new_int(0) 
var arrIdmLBar = array.new_int(0) 
var arrLastH = array.new_float(0) 
var arrLastHBar = array.new_int(0) 
var arrLastL = array.new_float(0) 
var arrLastLBar = array.new_int(0) 
var arrIdmLine = array.new_line(0)
var arrIdmLabel = array.new_label(0)
var arrBCLine = array.new_line(0)
var arrBCLabel = array.new_label(0)
var arrHLLabel = array.new_label(0)
var arrHLCircle = array.new_label(0)

// Mảng quản lý label volume cho Major và Internal
var arrHLVolLabel = array.new_label(0)
// Mảng cho Gann waves (F1, SF1)
var arrGannVolLabel = array.new_label(0)

//color
color transp = color.new(color.gray,100)

thongbao(_message) =>
    log.info(str.tostring(_message))

showmang(array) =>
    if(array.size(array) > 0)
        thongbao("---- Show Mang: ----")
        for i=0 to array.size(array) - 1
            thongbao( "phan tu "+str.tostring(i)+" : "+ str.tostring(array.get(array,i)) +' -- ')

// Hiển thị loại nến ra chart và thông báo khi ở vùng Mua hoặc bán
activeAlert(_message) =>
    alert(syminfo.ticker + ":" + timeframe.period + "m => "+ _message)

alertBarPattern(string zone, string str, bool mustAlert = false) =>
    // zone : bull or bear or na
    // dinh nghia toan bo nen la false
    osb_up = false
    osb_down = false
    eng_up = false
    eng_down = false
    
    // mm_up = false
    // mm_down = false

    // doji_up_black = false
    // doji_up_blue = false
    // doji_up = false

    // doji_down_black = false
    // doji_down_blue = false
    // doji_down = false

    // dinh nghia sweep nen 
    sweep_low = false
    sweep_high = false

    back_wick_high = high[1]
    back_body_high = open[1] >= close[1] ? open[1] : close[1]
    back_body_low = open[1] >= close[1] ? close[1] : open[1]
    back_wick_low = low[1]

    now_wick_high = high
    now_body_high = open >= close ? open : close
    now_body_low = open >= close ? close : open
    now_wick_low = low 

    // Kiem tra nen la loai gi
    float Candle_body = 0.5
    //scan nến osb-eng - mera mera - csw//
    candle_up =  close > open
    candle_down =  close < open

    osb_up := high > high[1] and low < low[1] and close > close[1]
    osb_down := high > high[1] and low < low[1] and close < close[1]

    eng_up := open >= close[1] and close > high[1] and (close - open) >= Candle_body*(high - low)
    eng_down := open <= close[1] and close < low[1] and (open - close) >= Candle_body*(high - low)

    // mm_up := osb_up and close[2] > close[3] and close[1] > close[2]
    // mm_down := osb_down and close[2] < close[3] and close[1] < close[2]

    // doji_up_blue := low <= low[1] and candle_up and (open - low) > (high - open) and (open - low) >= Candle_body*(high - low) 
    // doji_up_black := low <= low[1] and candle_down and (close - low) > (high - close) and (close - low) >= Candle_body*(high - low)
    // doji_up := doji_up_blue or doji_up_black

    // doji_down_black := candle_down and high >= high[1] and (high - open) > (low - open) and (high - open) >= Candle_body*(high - low)
    // doji_down_blue := candle_up and high >= high[1] and (high - close) > (close - open) and (high - close) >= Candle_body*(high - low)
    // doji_down := doji_down_black or doji_down_blue

    sweep_low := back_wick_low > now_wick_low and (back_body_low <= now_body_low or back_wick_low < now_body_low)
    sweep_high := back_wick_high < now_wick_high and (back_body_high >= now_body_high or back_wick_high > now_body_high)

    // setup text và thông báo
    var textEntry = 'na'
    var textAlert = 'na'
    var last_text = ''
    timeStyleShapeTrade = label.style_none
    position_candle = yloc.abovebar
    if zone == 'na'
        if (osb_up and barstate.isconfirmed)
            textEntry := 'OSC ⇑'
            textAlert := 'Order Single Candle ⇑'
            position_candle := yloc.belowbar
        else if (osb_down and barstate.isconfirmed)
            textEntry := 'OSC ⇓'
            textAlert := 'Order Single Candle ⇓'
        // else if (doji_up and barstate.isconfirmed)
        //     textEntry := 'DJ ⇑'
        //     textAlert := 'Doji ⇑'
        //     position_candle := yloc.belowbar
        // else if (doji_down and barstate.isconfirmed)
        //     textEntry := 'DJ ⇓'
        //     textAlert := 'Doji ⇓'
        else if (sweep_high and barstate.isconfirmed)
            textEntry := 'SW ⇓'
            textAlert := 'Sweep high back candle ⇓'
        else if (sweep_low and barstate.isconfirmed)
            textEntry := 'SL ⇑'
            textAlert := 'Sweep low back candle ⇑'
            position_candle := yloc.belowbar

    else if zone == 'bull'
        if (osb_up and barstate.isconfirmed)
            textEntry := 'OSC ⇑'
            textAlert := 'Order Single Candle ⇑'
            position_candle := yloc.belowbar
        else if (eng_up and barstate.isconfirmed)
            textEntry := 'EG ⇑'
            textAlert := 'Engulfing ⇑'
            position_candle := yloc.belowbar
        // else if (doji_up and barstate.isconfirmed)
        //     textEntry := 'DJ ⇑'
        //     textAlert := 'Doji ⇑'
        //     position_candle := yloc.belowbar
        else if (sweep_low and barstate.isconfirmed)
            textEntry := 'SL ⇑'
            textAlert := 'Sweep low back candle ⇑'
            position_candle := yloc.belowbar
        
    else if zone == 'bear'
        if (osb_down and barstate.isconfirmed)
            textEntry := 'OSC ⇓'
            textAlert := 'Order Single Candle ⇓'
        else if (eng_down and barstate.isconfirmed)
            textEntry := 'EG ⇓'
            textAlert := 'Engulfing ⇓'
        // else if (doji_down and barstate.isconfirmed)
        //     textEntry := 'DJ ⇓'
        //     textAlert := 'Doji ⇓'
        else if (sweep_high and barstate.isconfirmed)
            textEntry := 'SW ⇓'
            textAlert := 'Sweep high back candle ⇓'

    volUltraHigh = getVolume()
    last_text := str
    // hiển thị ra chart
    if barstate.isconfirmed and textEntry != 'na' 
        last_text := last_text + '; ' + textAlert
        if showLabelCandle
            textEntry := volUltraHigh ? textEntry + 'V' : textEntry
            label.new(bar_index, high, textEntry, yloc=position_candle, color=colorLabelCandle, style=timeStyleShapeTrade, textcolor=colorLabelCandle, size=sizeCandleUltraVolume)
        activeAlert(last_text)
    else if mustAlert        
        activeAlert(last_text)
    textEntry := 'na'
    textAlert := 'na'
    last_text := ''
    volUltraHigh := false
// end test function

//Caculate
curTf = timeframe.in_seconds(timeframe.period)
dayTf = timeframe.in_seconds("1D")
i_loop = 2*dayTf/curTf
[pdh, pdl]  = request.security(syminfo.tickerid, 'D', [high[1], low[1]])
len = curTf*1000

//#endregion

//#region Inside Bar
isb = motherHigh > high and motherLow < low
if isb
    motherHigh := motherHigh
    motherLow := motherLow
    motherBar := motherBar
else
    motherHigh := high
    motherLow := low
    motherBar := time

//#endregion

//#region drawing function
isGreenBar(int bar) => close[bar] > open[bar]

textCenter(int left, int right) => int(math.avg(left,right))

getStyleLabel(bool style) => style ? label.style_label_down : label.style_label_up

getStyleArrow(bool style) => style ? label.style_arrowdown : label.style_arrowup

getYloc(bool style) =>  style ? yloc.abovebar : yloc.belowbar

getDirection(bool trend, int HBar, int LBar, float H, float L) =>
    x = trend ? HBar : LBar
    y = trend ? H : L
    [x, y]

getTextLabel(float current, float last, string same, string diff) => current > last ? same : diff

getPdhlBar(float value) =>
    int x = na
    if value == pdh
        for i = i_loop to 1 by 1
            if (high[i] == pdh)
                x := time[i]
                break
    else
        for i = i_loop to 1 by 1
            if (low[i] == pdl)
                x := time[i]
                break
    x

updateTopBotValue() =>
    array.push(arrTop, high)
    array.push(arrBot, low) 
    array.push(arrTopBotBar, time) 

updateLastHLValue() =>
    array.push(arrLastH,lastH)
    array.push(arrLastHBar,lastHBar)
    array.push(arrLastL,lastL)
    array.push(arrLastLBar,lastLBar)

updateIdmHigh() =>
    if low < L
        array.push(arrIdmHigh,puHigh)
        array.push(arrIdmHBar,puHBar)

updateIdmLow() =>
    if high > H
        array.push(arrIdmLow,puLow)
        array.push(arrIdmLBar,puLBar)  

getNLastValue(arr, n) =>
    if array.size(arr) > n - 1
        array.get(arr, array.size(arr) - n) 

removeNLastLabel(arr, n) =>
    if array.size(arr) > n - 1
        label.delete(array.get(arr, array.size(arr) - n))

removeNLastLine(arr, n) =>
    if array.size(arr) > n - 1
        line.delete(array.get(arr, array.size(arr) - n))    

removeLastLabel(arr, n) =>
    if array.size(arr) > n - 1
        for i = 1 to n
            label.delete(array.get(arr, array.size(arr) - i))

removeLastLine(arr, n) =>
    if array.size(arr) > n - 1
        for i = 1 to n
            line.delete(array.get(arr, array.size(arr) - i))

fixStrcAfterBos() =>
    removeLastLabel(arrBCLabel, 1)
    removeLastLine(arrBCLine, 1)
    removeLastLabel(arrIdmLabel, 1)
    removeLastLine(arrIdmLine, 1)
    removeLastLabel(arrHLLabel, 2)
    removeLastLabel(arrHLCircle, 2)
    // Xóa label volume
    removeLastLabel(arrHLVolLabel, 2)

fixStrcAfterChoch() =>
    removeLastLabel(arrBCLabel, 2)
    removeLastLine(arrBCLine, 2)
    removeNLastLabel(arrHLLabel, 2)
    removeNLastLabel(arrHLLabel, 3)
    removeNLastLabel(arrHLCircle, 2)
    removeNLastLabel(arrHLCircle, 3)
    removeNLastLabel(arrIdmLabel, 2)
    removeNLastLine(arrIdmLine, 2)
    // Xóa label volume
    removeNLastLabel(arrHLVolLabel, 2)
    removeNLastLabel(arrHLVolLabel, 3)

drawIDM(bool trend) =>
    [x, y] = getDirection(trend, idmLBar, idmHBar, idmLow, idmHigh)
    _direction = trend ? " ⇑ " : " ⇓ " 
    zone = trend ? "bull" : "bear" 
    colorText = trend and H_lastH > L_lastHH or not trend and H_lastLL > L_lastL ? color.red : colorIDM
    if showIDM
        ln = line.new(x, y, time, y, xloc.bar_time, color = colorIDM, style = line.style_dotted)
        lbl = label.new(textCenter(time, x), y, IDM_TEXT, xloc.bar_time, color = transp, textcolor = colorText, style = getStyleLabel(not trend), size = size.tiny)
        if barstate.isconfirmed
            str = _direction + " Price Cross "+ IDM_TEXT + (getVolume() ? ULTRAVOLUME : '')
            alertBarPattern(zone,str,true)
        array.push(arrIdmLine,ln)
        array.push(arrIdmLabel,lbl)
    array.clear(trend ? arrIdmLow : arrIdmHigh)    
    array.clear(trend ? arrIdmLBar : arrIdmHBar)

drawStructure(name, trend) =>
    [x, y] = getDirection(trend, lastHBar, lastLBar, lastH, lastL)
    color = trend ? bull : bear
    _direction = trend ? " ⇑ " : " ⇓ "
    zone = trend ? "bull" : "bear"
    if name == "BOS" and showBOS
        ln = line.new(x, y, time, y, xloc.bar_time, color = color, style = line.style_dashed)
        lbl = label.new(textCenter(time, x), y, BOS_TEXT, xloc.bar_time, color = transp, style = getStyleLabel(trend), textcolor = color, size = size.tiny)
        array.push(arrBCLine,ln)
        array.push(arrBCLabel,lbl)
        if barstate.isconfirmed
            str = BOS_TEXT + _direction + (getVolume() ? ULTRAVOLUME : '')
            alertBarPattern(zone,str,true)
    if name == "ChoCh" and showChoChStructure
        ln = line.new(x, y, time, y, xloc.bar_time, color = color, style = line.style_dashed)
        lbl = label.new(textCenter(time, x), y, CHOCH_TEXT, xloc.bar_time, color = transp, style = getStyleLabel(trend), textcolor = color, size = size.tiny)
        array.push(arrBCLine,ln)
        array.push(arrBCLabel,lbl)
        if barstate.isconfirmed
            str = CHOCH_TEXT + _direction + (getVolume() ? ULTRAVOLUME: '')
            alertBarPattern(zone,str,true)

drawLiveStrc(bool condition, bool direction, color color1, color color2, string txt, int length) =>
    if condition and barstate.islast
        colorText = direction ? color1 : color2
        [x, y] = if txt == IDM_TEXT
            getDirection(direction, idmHBar, idmLBar, idmHigh, idmLow)
        else
            getDirection(direction, lastHBar, lastLBar, lastH, lastL)
        _txt = txt + " - " + str.tostring(y)
        ln = line.new(x, y, time + len*length, y, xloc.bar_time, color = colorIDM, style = line.style_dotted),
        lbl = label.new(time + len*length, y, _txt, xloc.bar_time, color = transp, textcolor = colorText, style = label.style_label_left, size = size.tiny)
        if x == x[1]
            line.delete(ln[1])
            label.delete(lbl[1])

drawPrevStrc(bool condition, string txt) =>
    [x, y, color, x2, style] = switch
        txt == PDH_TEXT => [getPdhlBar(pdh), pdh, bull, time + len*lengPdh, line.style_solid]
        txt == PDL_TEXT => [getPdhlBar(pdl), pdl, bear, time + len*lengPdl, line.style_solid]
        txt == MID_TEXT => [math.min(lastLBar, lastHBar), math.avg(lastL, lastH), colorIDM, time + len*lengMid, line.style_dotted]
    _txt = txt + " - " + str.tostring(y)
    if condition and barstate.islast
        ln = line.new(x, y, x2, y, xloc.bar_time, color = color, style = style)
        lbl = label.new(x2, y, _txt, xloc.bar_time, color = transp, textcolor = color, style = label.style_label_left, size = size.small)
        if x == x[1]
            line.delete(ln[1])
            label.delete(lbl[1])

labelMn(bool trend) =>
    // Lấy tọa độ X (thời gian) và Y (giá) của đỉnh/đáy hiện tại
    [x, y] = getDirection(trend, puHBar, puLBar, puHigh, puLow)
    color_mn = trend ? puDnco : puUpco
    
    // 1. Vẽ mũi tên cấu trúc (Sẵn có)
    if showMn 
        lblArrow = label.new(x, y, "", xloc.bar_time, getYloc(trend), color_mn, getStyleArrow(trend), size = size_internalStructure)
        array.push(arrMnLabel, lblArrow)

    // 2. TÍNH VOLUME CHO SÓNG GANN (Phần bổ sung/sửa lỗi)
    if showVolGann // showVolGann là input bạn đã định nghĩa ở 
        // Xác định mốc thời gian bắt đầu (là đỉnh/đáy Internal trước đó)
        float t_start = trend ? lastInternalLowTime : lastInternalHighTime
        float t_end   = x // Thời gian của đỉnh/đáy hiện tại
        
        float _vGann = 0.0
        if not na(t_start) and not na(t_end)
            _vGann := f_get_range_volume(t_start, t_end)
            
        // Định dạng văn bản Volume (ví dụ: 10.5K hoặc 500)
        string volText = _vGann > 0 ? (_vGann >= 1000 ? str.tostring(_vGann / 1000, "#.##") + "K" : str.tostring(_vGann, "#")) : ""
        
        if volText != ""
            // Vẽ label Volume cho sóng Gann
            // Đặt label ngược hướng với mũi tên để tránh đè lên nhau
            lblVolGann = label.new(x, y, volText, 
                 xloc.bar_time, 
                 yloc = trend ? yloc.abovebar : yloc.belowbar, 
                 color = color.new(color.white, 100), 
                 textcolor = color_mn, 
                 style = trend ? label.style_label_down : label.style_label_up, 
                 size = size.tiny)
            array.push(arrGannVolLabel, lblVolGann)

labelHL(bool trend) =>
    [x, y] = getDirection(trend, HBar, LBar, H, L)
    txt = trend ? getTextLabel(H, getNLastValue(arrLastH, 1), "HH", "LH") : getTextLabel(L, getNLastValue(arrLastL, 1), "HL", "LL")
    
    // Tính volume (cộng dồn từ chân sóng trước)
    float t_start = trend ? lastLBar : lastHBar
    float t_end   = trend ? HBar : LBar
    float _v = 0.0
    if not na(t_start) and not na(t_end)
        _v := f_get_range_volume(t_start, t_end)
    string volText = _v > 0 ? (_v >= 1000 ? str.tostring(_v / 1000, "#.##") + "K" : str.tostring(_v, "#")) : ""
    string volDisplay = volText != "" ? "(" + volText + ")" : ""
    
    // Vẽ label chính
    if showHL
        lbl = label.new(x, y, txt, xloc.bar_time, color = transp, textcolor = colorHL, style = getStyleLabel(trend), size = size.small)
        array.push(arrHLLabel, lbl)
    if showCircleHL
        lbl2 = label.new(x, y, '', xloc.bar_time, getYloc(trend), color = trend ? bull : bear, style = label.style_circle, size = size.tiny)     
        array.push(arrHLCircle, lbl2)
    
    // Vẽ volume: đặt ở cùng x, lệch y một khoảng theo hướng ngược lại
    if volDisplay != "" and showVolMajor
        float offset = (high - low) * 0.5   // 50% chiều cao nến hiện tại
        float yVol = trend ? y + offset : y - offset
        yVol := trend ? math.min(yVol, high * 1.02) : math.max(yVol, low * 0.98)   // giới hạn an toàn
        
        lblVol = label.new(x, yVol, volDisplay, xloc.bar_time,
                           color = color.new(color.black, 100),
                           textcolor = color.new(color.black, 0),
                           style = getStyleLabel(not trend),   // style ngược lại để dễ phân biệt
                           size = size.small)
        array.push(arrHLVolLabel, lblVol)

sweepHL(bool trend) =>
    [x, y] = getDirection(trend, lastHBar, lastLBar, lastH, lastL)
    _direction = trend ?  " ⇓ " : " ⇑ " 
    zone = trend ? "bear" : "bull"
    if showSw
        line.new(x, y, time, y, xloc.bar_time, color = colorSweep, style = line.style_dotted)
        if markX
            label.new(textCenter(time, x), y, "x", xloc.bar_time, color = transp, textcolor = colorSweep, style = getStyleLabel(trend), size = size.tiny)
            if barstate.isconfirmed
                str = _direction +" Sweeping..."+ (getVolume() ? ULTRAVOLUME: '')    
                alertBarPattern(zone,str,true)

TP(H, L) =>
    target = isCocUp ? high + math.abs(H - L) : low - math.abs(H - L)
    target := target < 0 ? 0 : target
    if showTP
        line.new(bar_index, isCocUp ? open : close, bar_index, target, color = colorTP, style = line.style_arrow_right)
        // line.new(bar_index, isCocUp ? high : low, bar_index, target, color = colorTP, style = line.style_arrow_right)  

createBox(left, right, top, bottom, color) => 
    box.new(left=left, right=right, top=top, bottom=bottom, xloc = xloc.bar_time, bgcolor=color, border_color=color, extend = extend.right)

removeZone(zoneArray, box zone) =>
    index = array.indexof(zoneArray, zone)
    box.delete(zone)
    array.remove(zoneArray, index)

marginZone(zone) => [box.get_top(zone), box.get_bottom(zone), box.get_left(zone)]

handleZone(zoneArray, left, top, bot, color) =>
    _top = top
    _bot = bot
    _left = left    

    zone = getNLastValue(zoneArray, 1)

    [topZone, botZone, leftZone] = marginZone(zone)
    rangeTop = math.abs(_top-topZone)/(topZone-botZone) < mergeRatio
    rangeBot = math.abs(_bot-botZone)/(topZone-botZone) < mergeRatio

    //Merge zone
    if _top >= topZone and _bot <= botZone or rangeTop or rangeBot
        _top := math.max(_top,topZone)
        _bot := math.min(_bot,botZone)
        _left := leftZone 
        removeZone(zoneArray, zone)     

    if not (_top <= topZone and _bot >= botZone)
        array.push(zoneArray, createBox(_left, time, _top, _bot, color))

processZones(zones, isSupply) =>
    if array.size(zones) > 0
        if(barstate.isconfirmed)
            iHigh = high
            iLow = low
            iClose = close
            iHighPre = high[1]
            iLowPre = low[1]
            iOpenPre = open[1]
            iClosePre = close[1]
            for i = array.size(zones) - 1 to 0 by 1
                zone = array.get(zones, i)
                [topZone, botZone, leftZone] = marginZone(zone)
                str = ''
                //bar = "High: " + str.tostring(iHigh) + " Low: " + str.tostring(iLow) + " Close: " + str.tostring(iClose)
                //bar2 = "High 2: " + str.tostring(iHighPre) + " Low 2: " + str.tostring(iLowPre) + " Close 2: " + str.tostring(iClosePre)
                //Breaker block zones
                if isSupply and low[1] < botZone and close[1] > topZone
                    array.push(demandZone, createBox(leftZone, time[1], topZone, botZone, colorDemand))
                    // if barstate.isconfirmed
                    //     str := "Price ⇑ reject supplyZone : " + str.tostring(topZone) + (getVolume() ? ULTRAVOLUME: '')
                    //     alertBarPattern('bear',str)
                else if not isSupply and high[1] > topZone and close[1] < botZone
                    array.push(supplyZone, createBox(leftZone, time[1], topZone, botZone, colorSupply))
                    // if barstate.isconfirmed
                    //     str := "Price ⇓ reject demandZone : " + str.tostring(botZone) + (getVolume() ? ULTRAVOLUME: '')
                    //     alertBarPattern('bull',str)

                //Mitigated zones
                // SupplyZone
                else if (isSupply and high[1] >= botZone and high[1] < topZone)
                    box.set_right(zone, time[1])
                    box.set_extend(zone,extend.none) 
                    box.set_bgcolor(zone, colorMitigated)
                    box.set_border_color(zone, colorMitigated)
                    topbot = str.tostring(topZone)+str.tostring(botZone)

                    // khong la isb ,nen truoc do la nen tang, gia dong cua phai nam duoi Low nen truoc, hoac sweept high nen truoc
                    // if (isb == false and iOpenPre < iClosePre and ((iClose < iHighPre or iLow < iLowPre) or (iHighPre < iHigh and iClose < iHighPre)) )
                    // Low O < Low, EG hoac sweept
                    if (isb == false and iLow < iLowPre and (iHighPre < topZone and iHighPre >= botZone and iClose < iLowPre) or (iHigh >= botZone and iHigh < topZone and iHigh > iHighPre and iClose < iHighPre and iClose > iClosePre))
                        
                        str := "POI supplyZone ⇓ (Bearish); "+ (getVolume() ? ULTRAVOLUME: '') +" Zone: top " + str.tostring(topZone)+" & bot " + str.tostring(botZone)
                        if ((iHighPre < topZone and iHighPre >= botZone and iClose < iLowPre)) 
                            str := str + "; Type: EG"
                        else if (iHigh >= botZone and iHigh < topZone and iHigh > iHighPre and iClose < iHighPre and iClose > iClosePre)
                            str := str + "; Type: Sweept"
                        // thongbao(str +" \n "+ bar+" \n "+ bar2)
                        thongbao(str)
                        alertBarPattern('bear',str)
                    
                    // if not array.includes(mitigatedZoneSupply, topbot)
                    // if barstate.isconfirmed
                    //     alertBarPattern('bear',"Mitigated supplyZone (Bearish): " + str.tostring(botZone))
                    // array.push(mitigatedZoneSupply, topbot)
                // Demand zone
                else if not isSupply and low[1] <= topZone and low[1] > botZone
                    box.set_right(zone, time[1])
                    box.set_extend(zone,extend.none) 
                    box.set_bgcolor(zone, colorMitigated)
                    box.set_border_color(zone, colorMitigated)
                    bottop = str.tostring(botZone)+str.tostring(topZone)

                    // khong la isb, nen truoc do la nen giam, gia dong cua phai nam tren High nen truoc, hoac sweept low nen truoc
                    // if (isb == false and iOpenPre > iClosePre and ((iClose > iLowPre or iHigh > iHighPre) or (iLowPre > iLow and iClose > iLowPre)))
                    // High < High O, EG hoac sweept
                    if (isb == false and iHigh > iHighPre and (iLowPre > botZone and iLowPre <= topZone and iClose > iHighPre) or (iLow >= botZone and iLow < topZone and iLow < iLowPre and iClose > iLowPre and iClose > iClosePre))

                        str := "POI demandZone ⇑ (Bullish); "+ (getVolume() ? ULTRAVOLUME: '') +" Zone: top " + str.tostring(topZone)+" & bot " + str.tostring(botZone) 
                        if (iLowPre > botZone and iLowPre <= topZone and iClose > iHighPre) 
                            str := str + "; ==> Type: EG"
                        else if (iLow >= botZone and iLow < topZone and iLow < iLowPre and iClose > iLowPre and iClose > iClosePre)
                            str := str + "; ==> Type: Sweept"
                        //thongbao(str +" \n "+ bar+" \n "+ bar2)
                        thongbao(str)
                        alertBarPattern('bull',str)
                    
                    // 
                    // if not array.includes(mitigatedZoneDemand, bottop)
                    //     alertBarPattern('bull',"Mitigated demandZone (Bullish):"+ str.tostring(topZone))
                    // array.push(mitigatedZoneDemand, bottop)

                // first_elem_str = "First element: " + str.tostring(array.size(mitigatedZoneDemand)) +' - '+ str.tostring((array.size(mitigatedZoneSupply)))
                // var tbl = table.new(position.top_right, 1, 1, color.yellow)

                // if (barstate.islast)
                //     // label.new(bar_index, high, first_elem_str)
                //     table.cell(tbl, 0, 0, first_elem_str)
                //Delete sweep zones     
                if (time[1] - leftZone > len*maxBarHistory) or (isSupply and high[1] >= topZone) or (not isSupply and low[1] <= botZone)
                    removeZone(zones, zone)
                    if array.size(mitigatedZoneDemand) > 0
                        array.pop(mitigatedZoneDemand)
                    if array.size(mitigatedZoneSupply) > 0    
                        array.pop(mitigatedZoneSupply)
                    // array.remove(array.first(mitigatedZoneDemand))                

scob(zones, isSupply) =>
    [topZone, botZone, leftZone] = marginZone(getNLastValue(zones, 1))
    avgV = ta.ema(volume, 20)
    spread = math.abs(close - open)
    if not isb[1] and ((volume[1] > avgV[1]) or (spread[1] < spread))
        if not isSupply and low[1] < low[2] and low[1] < low and close > high[1] and low[1] > botZone and low[1] < topZone
            scobUp
        else if isSupply and high[1] > high[2] and high[1] > high and close < low[1] and high[1] < topZone and high[1] > botZone
            scobDn
        else
            na  

//#endregion

//#region get value from array
top = getNLastValue(arrTop, 1)
bot = getNLastValue(arrBot, 1)
topBotBar = getNLastValue(arrTopBotBar, 1)

top1 = getNLastValue(arrTop, 2)
bot1 = getNLastValue(arrBot, 2)
topBotBar1 = getNLastValue(arrTopBotBar, 2)
//#endregion

//#region Outside Bar
osb = high > top and low < bot

//#endregion

//#region Minor Structure
if high >= top and low <= bot //notrend
    updateIdmHigh() 
    updateIdmLow()   
    if not na(mnStrc)
        prevMnStrc := mnStrc ? true : false
    else 
        if prevMnStrc and isGreenBar(0) and not isGreenBar(1)
            puHigh := top
            puHBar := topBotBar
            labelMn(true)
            if not na(puHBar)
                lastInternalHighTime := puHBar
            labelMn(false)
            if not na(puLBar)
                lastInternalLowTime := puLBar
            if high > H
                array.push(arrIdmLow,low)
                array.push(arrIdmLBar,time)

        if not prevMnStrc and not isGreenBar(0) and isGreenBar(1)
            puLow := bot
            puLBar := topBotBar
            labelMn(true)
            if not na(puHBar)
                lastInternalHighTime := puHBar
            labelMn(false)
            if not na(puLBar)
                lastInternalLowTime := puLBar
            if low < L
                array.push(arrIdmHigh,high)
                array.push(arrIdmHBar,time)
                
    updateTopBotValue()   
    puHigh := high
    puLow := low
    puHBar := time
    puLBar := time
    mnStrc := na

if high >= top and low > bot //uptrend
    if prevMnStrc and na(mnStrc)
        puHigh := top1
        puHBar := topBotBar1
        labelMn(true)
        if not na(puHBar)
            lastInternalHighTime := puHBar
        labelMn(false)
        if not na(puLBar)
            lastInternalLowTime := puLBar
    else if (not prevMnStrc and na(mnStrc)) or not mnStrc
        labelMn(false)
        if not na(puLBar)
            lastInternalLowTime := puLBar   

    updateIdmLow()   
    updateTopBotValue()  
    puHigh := high
    puHBar := time
    prevMnStrc := na
    mnStrc := true

if high < top and low <= bot //downtrend
    if not prevMnStrc and na(mnStrc)
        puLow := bot1
        puLBar := topBotBar1
        labelMn(false)
        if not na(puLBar)
            lastInternalLowTime := puLBar
        labelMn(true)
        if not na(puHBar)
            lastInternalHighTime := puHBar
    else if (prevMnStrc and na(mnStrc)) or mnStrc
        labelMn(true)
        if not na(puHBar)
            lastInternalHighTime := puHBar 

    updateIdmHigh() 
    updateTopBotValue()   
    puLow := low  
    puLBar := time 
    prevMnStrc := na
    mnStrc := false
//#endregion

//#region update IDM
if high >= H
    H := high
    HBar := time
    L_lastHH := low  
    idmLow := getNLastValue(arrIdmLow, 1)
    idmLBar := getNLastValue(arrIdmLBar, 1)

if low <= L
    L := low
    LBar := time
    H_lastLL := high
    idmHigh := getNLastValue(arrIdmHigh, 1)
    idmHBar := getNLastValue(arrIdmHBar, 1)

//#endregion

// #region structure mapping
// Check for IDM
if findIDM and isCocUp and isCocUp
    if low < idmLow 
        findIDM := false
        isBosUp := false
        L := low
        LBar := time 
        if structure_type == "Choch with IDM" and idmLow == lastL
            if isPrevBos
                fixStrcAfterBos() 
                lastL := getNLastValue(arrLastL, 1)    
                lastLBar := getNLastValue(arrLastLBar, 1)    
                lastH := H
                lastHBar := HBar
                drawIDM(true)
                labelHL(true) //Confirm HH
                updateLastHLValue()
                H_lastH := getNLastValue(arrLastH, 1)
            else
                fixStrcAfterChoch()
                isCocUp := false
                isCocDn := true
        else
            lastH := H
            lastHBar := HBar
            drawIDM(true)
            labelHL(true) //Confirm HH
            updateLastHLValue()
            H_lastH := getNLastValue(arrLastH, 1)


if findIDM and isCocDn and isBosDn
    if high > idmHigh
        findIDM := false
        isBosDn := false
        H := high
        HBar := time
        if structure_type == "Choch with IDM" and idmHigh == lastH
            if isPrevBos
                fixStrcAfterBos()
                lastH := getNLastValue(arrLastH, 1)    
                lastHBar := getNLastValue(arrLastHBar, 1)   
                lastL := L
                lastLBar := LBar
                drawIDM(false) 
                labelHL(false) //Confirm LL
                updateLastHLValue()
                L_lastL := getNLastValue(arrLastL, 1)
            else
                fixStrcAfterChoch()  
                isCocUp := true
                isCocDn := false  
        else
            lastL := L
            lastLBar := LBar
            drawIDM(false) 
            labelHL(false) //Confirm LL
            updateLastHLValue()
            L_lastL := getNLastValue(arrLastL, 1)


//Check for ChoCh
if isCocDn and high > lastH
    if structure_type == "Choch without IDM" and idmHigh == lastH and close > idmHigh
        removeLastLabel(arrIdmLabel, 1)
        removeLastLine(arrIdmLine, 1)
    if close > lastH 
        drawStructure("ChoCh", true) //Confirm CocUp 
        findIDM := true
        isBosUp := true
        isCocUp := true
        isBosDn := false
        isCocDn := false
        isPrevBos := false
        L_lastL := getNLastValue(arrLastL, 1) 
        TP(lastH,lastL)
    else
        if idmHigh == lastH
            removeLastLine(arrIdmLine, 1)
        sweepHL(true)
      
if isCocUp and low < lastL
    if structure_type == "Choch without IDM" and idmLow == lastL and close < idmLow
        removeLastLabel(arrIdmLabel, 1)
        removeLastLine(arrIdmLine, 1)
    if close < lastL
        drawStructure("ChoCh", false)  //Confirm CocDn
        findIDM := true
        isBosUp := false
        isCocUp := false
        isBosDn := true
        isCocDn := true
        isPrevBos := false
        H_lastH := getNLastValue(arrLastH, 1)
        TP(lastH,lastL)
    else
        if idmLow == lastL
            removeLastLine(arrIdmLine, 1)
        sweepHL(false)

//Check for BoS
if not findIDM and not isBosUp and isCocUp
    if high > lastH
        if close > lastH
            drawStructure("BOS", true)  //Confirm BosUp
            findIDM := true
            isBosUp := true
            isCocUp := true
            isBosDn := false
            isCocDn := false
            isPrevBos := true
            labelHL(false) //Confirm HL
            lastL := L
            lastLBar := LBar
            L_lastL := L
            TP(lastH,lastL)
        else
            sweepHL(true)

if not findIDM and not isBosDn and isCocDn 
    if low < lastL
        if close < lastL
            drawStructure("BOS", false)  //Confirm BosDn
            findIDM := true
            isBosUp := false
            isCocUp := false
            isBosDn := true
            isCocDn := true
            isPrevBos := true
            labelHL(true) //Confirm LH
            lastH := H
            lastHBar := HBar
            H_lastH := H
            TP(lastH,lastL)
        else
            sweepHL(false)
//#endregion

//#trigger update High and Low 
if high > lastH
    lastH := high
    lastHBar := time

if low < lastL
    lastL := low
    lastLBar := time

//#endregion
if showPOI
    if isSweepOBS
        if low_MOBS > high[2]
            if high_MOBS < high[3] and low[3] > high[1]
                handleZone(supplyZone, time[3], high[3], low[3], colorSupply)
            else
                handleZone(supplyZone, current_OBS, high_MOBS, low_MOBS, colorSupply) 
            if high_MOBS > high[3] and low[3] > high[1]
                handleZone(supplyZone, time[3], high[2], low[3], colorSupply)    
            isSweepOBS := false
        else 
            if poi_type == "Mother Bar" and isb[3]
                high_MOBS := math.max(high_MOBS,motherHigh[3])
                low_MOBS := math.min(low_MOBS,motherLow[3])
                current_OBS := math.min(current_OBS,motherBar)    
            else
                high_MOBS := high[3]
                low_MOBS := low[3]
                current_OBS := time[3]
    else
        high_MOBS := high[4]
        low_MOBS := low[4]
        current_OBS := time[4]
        if high_MOBS > high[5] and high_MOBS > high[3]
            isSweepOBS := true
                
            
    if isSweepOBD
        if high_MOBD < low[2]
            if low[3] < low_MOBD and high[3] < low[1]
                handleZone(demandZone, time[3], high[3], low[3], colorDemand)
            else
                handleZone(demandZone, current_OBD, high_MOBD, low_MOBD, colorDemand)
            if low_MOBD < low[3] and high[3] < low[1]
                handleZone(demandZone, time[3], high[3], low[2], colorDemand)
            isSweepOBD := false
        else 
            if poi_type == "Mother Bar" and isb[3]
                high_MOBD := math.max(high_MOBD,motherHigh[3])
                low_MOBD := math.min(low_MOBD,motherLow[3])
                current_OBD := math.min(current_OBD,motherBar)        
            else
                high_MOBD := high[3]
                low_MOBD := low[3]
                current_OBD := time[3] 
    else
        low_MOBD := low[4]
        high_MOBD := high[4]
        current_OBD := time[4]
        if low_MOBD < low[5] and low_MOBD < low[3]
            isSweepOBD := true
           
//#endregion

// test function
checkBarPattern() =>
    // zone = 'bull'
    //zone = 'bear'
    zone = 'na'
    alertBarPattern(zone,'Candle')

// test function
test() =>
    // zone = 'bull'
    //zone = 'bear'
    zone = 'na'
    alertBarPattern(zone,'T : ',true)

//#region run function
barcolor(showSCOB ? scob(supplyZone, true) : na, -1)
barcolor(showSCOB ? scob(demandZone, false) : na, -1)
barcolor(showISB and isb ? colorISB : na, 0,title="InSide Bar")
barcolor(osb and isGreenBar(0) and showOSB ? colorOSB_up_Structure : na)
barcolor(osb and not isGreenBar(0) and showOSB ? colorOSB_down_Structure : na) 
processZones(supplyZone, true)
processZones(demandZone, false)
drawLiveStrc(showliveIDM and findIDM, not isCocUp, colorIDM, colorIDM, IDM_TEXT, lengIDM)
drawLiveStrc(showliveChoch, not isCocUp, bull, bear, CHOCH_TEXT, lengChoch)
drawLiveStrc(showliveBOS and not findIDM, isCocUp, bull, bear, BOS_TEXT, lengBos)
drawPrevStrc(showPdh, PDH_TEXT)
drawPrevStrc(showPdl, PDL_TEXT)
drawPrevStrc(showMid, MID_TEXT)
// checkBarPattern()
//test()
//#endregion

//----- Gann wave-------
// --- INPUTS ---
DefaultGannMLSetting = "Cấu hình Sóng F1"
showF1GannML        = input.bool(true,  "Hiển thị F1 (Dotted)", group = DefaultGannMLSetting, inline = "f1")
showSF1GannML       = input.bool(true,  "Hiển thị S-F1 (Solid)", group = DefaultGannMLSetting, inline = "f1")
colorF1GannML       = input.color(#2c2c2c, "", group = DefaultGannMLSetting, inline = "f1")
showChoChF1GannML   = input.bool(true,  "CHoCH F1", group = DefaultGannMLSetting, inline = "choch")
showChoChSF1GannML  = input.bool(true,  "CHoCH S-F1", group = DefaultGannMLSetting, inline = "choch")

// --- FUNCTIONS ---
alertGann(string str) =>
    alert(syminfo.ticker + ":" + timeframe.period + "m => " + str)

// Tính toán sóng Gann cơ bản
f_CalculateFn1(_pointX, _pointY, arrayXn, arrayYn, arrayXFn1, arrayYFn1) =>
    int _resultdraw = 0
    if((array.get(arrayYFn1,0) >= array.get(arrayYFn1,1) and array.get(arrayYn,0) < array.get(arrayYFn1,1)) or (array.get(arrayYFn1,0) <= array.get(arrayYFn1,1) and array.get(arrayYn,0) > array.get(arrayYFn1,1)))
        array.unshift(arrayXFn1,_pointX), array.unshift(arrayYFn1,_pointY), _resultdraw := 1
    else if(array.get(arrayYFn1,0) >= array.get(arrayYFn1,1) and (array.get(arrayYn,0) < array.get(arrayYn,1) and array.get(arrayYn,0) < array.get(arrayYn,2) and array.get(arrayYn,1) < array.get(arrayYn,3)) and array.get(arrayYn,2) < array.get(arrayYn,3))
        array.unshift(arrayXFn1,_pointX), array.unshift(arrayYFn1,_pointY), _resultdraw := 1
    else if(array.get(arrayYFn1,0) <= array.get(arrayYFn1,1) and (array.get(arrayYn,0) > array.get(arrayYn,1) and array.get(arrayYn,0) > array.get(arrayYn,2) and array.get(arrayYn,1) > array.get(arrayYn,3)) and array.get(arrayYn,2) > array.get(arrayYn,3))
        array.unshift(arrayXFn1,_pointX), array.unshift(arrayYFn1,_pointY), _resultdraw := 1
    else if(array.get(arrayYFn1,0) >= array.get(arrayYFn1,1) and _pointY >= array.get(arrayYFn1,0) and (_pointY >= array.get(arrayYFn1,0) or (array.get(arrayYn,0) > array.get(arrayYn,2) and array.get(arrayYn,2) > array.get(arrayYn,1)) or (array.get(arrayYn,0) > array.get(arrayYn,1) and array.get(arrayYn,1) > array.get(arrayYn,2) and array.get(arrayYn,2) > array.get(arrayYn,3))))
        array.set(arrayXFn1, 0, _pointX), array.set(arrayYFn1, 0, _pointY), _resultdraw := 2
    else if(array.get(arrayYFn1,0) <= array.get(arrayYFn1,1) and _pointY <= array.get(arrayYFn1,0) and (_pointY <= array.get(arrayYFn1,0) or (array.get(arrayYn,0) < array.get(arrayYn,2) and array.get(arrayYn,2) < array.get(arrayYn,1)) or (array.get(arrayYn,0) < array.get(arrayYn,1) and array.get(arrayYn,1) < array.get(arrayYn,2) and array.get(arrayYn,2) < array.get(arrayYn,3))))
        array.set(arrayXFn1, 0, _pointX), array.set(arrayYFn1, 0, _pointY), _resultdraw := 2
    [_resultdraw]

// Tính toán sóng phụ (Sub-swing)
f_CalculateSFn1(_pointX, _pointY, _arrayMark, _mIdxMark, arraySXFn1, arraySYFn1) =>
    int _resultdraw = 0
    if(array.get(_arrayMark,_mIdxMark) == 0)
        if((array.get(arraySYFn1,0) >= array.get(arraySYFn1,1) and _pointY >= array.get(arraySYFn1,0)) or (array.get(arraySYFn1,0) <= array.get(arraySYFn1,1) and _pointY <= array.get(arraySYFn1,0)))
            array.set(arraySXFn1, 0, _pointX), array.set(arraySYFn1, 0, _pointY), _resultdraw := 2
        else if((array.get(arraySYFn1,0) >= array.get(arraySYFn1,1) and _pointY < array.get(arraySYFn1,1)) or (array.get(arraySYFn1,0) <= array.get(arraySYFn1,1) and _pointY > array.get(arraySYFn1,1)))
            array.unshift(arraySXFn1,_pointX), array.unshift(arraySYFn1,_pointY), _resultdraw := 1
        else if((array.get(arraySYFn1,0) >= array.get(arraySYFn1,1) and _pointY < array.get(arraySYFn1,0) and _pointY > array.get(arraySYFn1,1)) or (array.get(arraySYFn1,0) <= array.get(arraySYFn1,1) and _pointY > array.get(arraySYFn1,0) and _pointY < array.get(arraySYFn1,1)))
            array.unshift(arraySXFn1,_pointX), array.unshift(arraySYFn1,_pointY), array.set(_arrayMark,_mIdxMark,_pointY), _resultdraw := 1
    else if(array.get(_arrayMark,_mIdxMark) > 0)
        if((array.get(arraySYFn1,0) >= array.get(arraySYFn1,1) and _pointY < array.get(arraySYFn1,1)) or (array.get(arraySYFn1,0) <= array.get(arraySYFn1,1) and _pointY > array.get(arraySYFn1,1)))
            array.unshift(arraySXFn1,_pointX), array.unshift(arraySYFn1,_pointY), array.set(_arrayMark,_mIdxMark,0), _resultdraw := 1
        else if((array.get(arraySYFn1,0) >= array.get(arraySYFn1,1) and _pointY > array.get(arraySYFn1,2)) or (array.get(arraySYFn1,0) <= array.get(arraySYFn1,1) and _pointY < array.get(arraySYFn1,2)))
            array.set(arraySXFn1, 0, _pointX), array.set(arraySYFn1, 0, _pointY), array.set(_arrayMark,_mIdxMark,0), _resultdraw := 2
        else if((array.get(arraySYFn1,0) >= array.get(arraySYFn1,1) and _pointY <= array.get(arraySYFn1,2) and _pointY >= array.get(arraySYFn1,0)) or (array.get(arraySYFn1,0) <= array.get(arraySYFn1,1) and _pointY >= array.get(arraySYFn1,2) and _pointY <= array.get(arraySYFn1,0)))
            array.set(arraySXFn1, 0, _pointX), array.set(arraySYFn1, 0, _pointY), array.set(_arrayMark,_mIdxMark,_pointY), _resultdraw := 2
    [_resultdraw]

f_CalculateRemoveDuplicateLine(arrayLine, arraySLine) =>
    if(array.size(arraySLine) > 1 and array.size(arrayLine) > 1)
        if(line.get_y1(array.get(arraySLine,1)) == line.get_y1(array.get(arrayLine,1)) and line.get_y2(array.get(arraySLine,1)) == line.get_y2(array.get(arrayLine,1)) and line.get_x1(array.get(arraySLine,1)) == line.get_x1(array.get(arrayLine,1)) and line.get_x2(array.get(arraySLine,1)) == line.get_x2(array.get(arrayLine,1)))
            line.delete(array.get(arrayLine,1))
            array.remove(arrayLine,1)

f_CalculateChoCh(arraySXFn1, arraySYFn1) =>
    int _X = 0, float _Y = 0
    if((array.get(arraySYFn1,3) > array.get(arraySYFn1,2) and array.get(arraySYFn1,3) < array.get(arraySYFn1,1) and array.get(arraySYFn1,0) < array.get(arraySYFn1,2)) or (array.get(arraySYFn1,3) < array.get(arraySYFn1,2) and array.get(arraySYFn1,3) > array.get(arraySYFn1,1) and array.get(arraySYFn1,0) > array.get(arraySYFn1,2)))
        _X := array.get(arraySXFn1,2), _Y := array.get(arraySYFn1,2)
    [_X, _Y]

f_CalculateDrawF(_drawF, arrayLine, arrayX, arrayY, showChoCh, arrayMChoCh, idxChoCh, _color) =>
    if(_drawF > 0)
        if(_drawF == 1)
            array.unshift(arrayLine, line.new(array.get(arrayX,1), array.get(arrayY,1), array.get(arrayX,0), array.get(arrayY,0), color = _color, xloc = xloc.bar_time, width = 1, style = line.style_dotted))
        else if(_drawF == 2 and array.size(arrayLine) > 0)
            line.set_xy2(array.get(arrayLine,0), array.get(arrayX,0), array.get(arrayY,0))
        [XCh, YCh] = f_CalculateChoCh(arrayX, arrayY)
        if(showChoCh and YCh > 0 and array.get(arrayMChoCh, idxChoCh) != YCh)
            array.set(arrayMChoCh, idxChoCh, YCh)
            line.new(XCh, YCh, time, YCh, color = _color, xloc = xloc.bar_time, width = 1, style = line.style_dotted)
            alertGann("i-Minor CHoCH")

f_CalculateDrawSF(_drawSF, arrayLine, arraySLine, arraySX, arraySY, showChoCh, arrayMChoCh, idxChoCh, _color) =>
    if(_drawSF > 0)
        if(_drawSF == 1)
            array.unshift(arraySLine, line.new(array.get(arraySX,1), array.get(arraySY,1), array.get(arraySX,0), array.get(arraySY,0), color = _color, xloc = xloc.bar_time, width = 1))
        else if(_drawSF == 2 and array.size(arraySLine) > 0)
            line.set_xy2(array.get(arraySLine,0), array.get(arraySX,0), array.get(arraySY,0))
        
        f_CalculateRemoveDuplicateLine(arrayLine, arraySLine)
        
        [XChS, YChS] = f_CalculateChoCh(arraySX, arraySY)
        
        // SỬA TẠI ĐÂY: Bỏ idxIdxChoCh =
        if(showChoCh and YChS > 0 and array.get(arrayMChoCh, idxChoCh) != YChS)
            array.set(arrayMChoCh, idxChoCh, YChS) 
            line.new(XChS, YChS, time, YChS, color = _color, xloc = xloc.bar_time, width = 1, style = line.style_dashed)
            alertGann("Minor CHoCH")

// --- ARRAYS & VARIABLES ---
var arrayMarkGannML      = array.new_float(1, 0) // Chỉ cần 1 chỉ số cho F1
var arrayMarkChoChGannML = array.new_float(2, 0) // Chỉ cần 2 chỉ số cho F1 và SF1
var arrayXF0GannML       = array.new_int(5, time)
var arrayYF0GannML       = array.new_float(5, close)

var arrayXF1GannML       = array.new_int(5, time)
var arrayYF1GannML       = array.new_float(5, close)
var arrayLineF1GannML    = array.new_line()

var arraySXF1GannML      = array.new_int(5, time)
var arraySYF1GannML      = array.new_float(5, close)
var arraySLineF1GannML   = array.new_line()

// --- MAIN EXECUTION ---
f_CalculateAndDrawFn1(_pointX, _pointY) =>
    array.unshift(arrayXF0GannML, _pointX)
    array.unshift(arrayYF0GannML, _pointY)
    
    if(showF1GannML or showSF1GannML)
        [drawF1] = f_CalculateFn1(_pointX, _pointY, arrayXF0GannML, arrayYF0GannML, arrayXF1GannML, arrayYF1GannML)
        f_CalculateDrawF(showF1GannML ? drawF1 : 0, arrayLineF1GannML, arrayXF1GannML, arrayYF1GannML, showChoChF1GannML, arrayMarkChoChGannML, 0, colorF1GannML)
        
        if(drawF1 > 0 and showSF1GannML)
            [drawSF1] = f_CalculateSFn1(_pointX, _pointY, arrayMarkGannML, 0, arraySXF1GannML, arraySYF1GannML)
            f_CalculateDrawSF(showSF1GannML ? drawSF1 : 0, arrayLineF1GannML, arraySLineF1GannML, arraySXF1GannML, arraySYF1GannML, showChoChSF1GannML, arrayMarkChoChGannML, 1, colorF1GannML)

f_CalculateAndDraw(_pointX, _pointY1, _pointY2) =>
    if((array.get(arrayYF0GannML,0) > _pointY1 and array.get(arrayYF0GannML,0) > _pointY2) or (array.get(arrayYF0GannML,0) < _pointY1 and array.get(arrayYF0GannML,0) < _pointY2))
        f_CalculateAndDrawFn1(time, array.get(arrayYF0GannML,1))
    f_CalculateAndDrawFn1(time, _pointY1)
    f_CalculateAndDrawFn1(time, _pointY2)

highPrev = high
lowPrev  = low
if(barstate.isconfirmed)
    if(high > highPrev[1] and low > lowPrev[1])
        f_CalculateAndDraw(time, low, high)
    else if(high < highPrev[1] and low < lowPrev[1])
        f_CalculateAndDraw(time, high, low)
    else if((high >= highPrev[1] and low < lowPrev[1]) or (high > highPrev[1] and low <= lowPrev[1]))
        if(close > open)
            f_CalculateAndDraw(time, low, high)
        else
            f_CalculateAndDraw(time, high, low)
    else if(high[0] <= highPrev[1] and low[0] >= lowPrev[1])
        highPrev := highPrev[1]
        lowPrev  := lowPrev[1]
// end region Gann wave

// ==========================================================
// BẢN FIX TRIỆT ĐỂ: CHỈ VẼ SÓNG CHUẨN (SHIFT HIGH/LOW)
// ==========================================================

// 1. Khai báo mảng bộ nhớ (Dùng tên biến hoàn toàn mới để tránh trùng)
var label[] vLabels = array.new_label(2, na) 
var float[] vValues = array.new_float(5, 0.0) // [0]:Giá, [1]:Time, [2]:State(0:H,1:L), [3]:Mốc phá vỡ, [4]:Đỉnh/Đáy cũ

float _p      = array.get(vValues, 0)
int   _t      = int(array.get(vValues, 1))
float _st     = array.get(vValues, 2) 
float _breakP = array.get(vValues, 3) 

// 2. Pivot 3 nến 
bool _h = high[1] > high[0] and high[1] > high[2]
bool _l = low[1] < low[0] and low[1] < low[2]

// 3. LOGIC XỬ LÝ ĐỈNH (SWING HIGH)
if _h
    // Nếu đang tìm Đỉnh (0) hoặc giá cao hơn đỉnh cũ (Dời đỉnh)
    if na(_st) or _st == 0.0 or (_st == 1.0 and high[1] > _p)
        if na(_p) or high[1] > _p
            label.delete(array.get(vLabels, 0))
            label.delete(array.get(vLabels, 1))
            
            float _vol = f_get_range_volume(_t, time[1])
            string _txt = _vol >= 1000 ? str.tostring(_vol / 1000, "#.#") + "K" : str.tostring(_vol, "#")
            
            // Vẽ mũi tên và volume (Dùng tham số tường minh để tránh lỗi)
            label _la = label.new(x=time[1], y=high[1], text="", xloc=xloc.bar_time, yloc=yloc.abovebar, color=puDnco, style=label.style_arrowdown, size=size_internalStructure)
            label _lv = na
            if showVolGann
                _lv := label.new(x=time[1], y=high[1], text=_txt, xloc=xloc.bar_time, yloc=yloc.abovebar, color=color.new(color.white, 100), textcolor=puDnco, style=label.style_label_down, size=size_internalStructure)
            
            array.set(vLabels, 0, _la)
            array.set(vLabels, 1, _lv)
            array.set(vValues, 0, high[1])
            array.set(vValues, 1, time[1])
            array.set(vValues, 2, 0.0)
            array.set(vValues, 3, low[1]) // Đặt mốc chặn: Muốn tạo đáy phải phá giá thấp nến này

// 4. LOGIC XỬ LÝ ĐÁY (SWING LOW)
if _l
    // Chỉ vẽ đáy nếu giá thấp hơn mốc chặn _breakP (Phá vỡ cấu trúc nến đỉnh)
    if (_st == 0.0 and low[1] < _breakP) or (_st == 1.0 and low[1] < _p)
        label.delete(array.get(vLabels, 0))
        label.delete(array.get(vLabels, 1))
            
        float _vol = f_get_range_volume(_t, time[1])
        string _txt = _vol >= 1000 ? str.tostring(_vol / 1000, "#.#") + "K" : str.tostring(_vol, "#")
        
        label _la = label.new(x=time[1], y=low[1], text="", xloc=xloc.bar_time, yloc=yloc.belowbar, color=puUpco, style=label.style_arrowup, size=size_internalStructure)
        label _lv = na
        if showVolGann
            _lv := label.new(x=time[1], y=low[1], text=_txt, xloc=xloc.bar_time, yloc=yloc.belowbar, color=color.new(color.white, 100), textcolor=puUpco, style=label.style_label_up, size=size_internalStructure)
            
        array.set(vLabels, 0, _la)
        array.set(vLabels, 1, _lv)
        array.set(vValues, 0, low[1])
        array.set(vValues, 1, time[1])
        array.set(vValues, 2, 1.0)
        array.set(vValues, 3, high[1]) // Đặt mốc chặn: Muốn tạo đỉnh phải phá giá cao nến này

Overview

Structure TA marks swing highs and lows, break of structure and change of character events, optionally with an inducement step, and shows internal structure and volume on major swings. It also draws previous day high and low lines, an optional equilibrium line, sweep lines with markers, target levels and live structure breaks. Optional session shading and a daily bias explanation can be switched on, with a seasonal time adjustment. It is based on another author's script credited in the code.

Details

Published by

trananh211 on GitHub

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

SMC / ICT / price action, Support & resistance

Markets

Any symbol

Timeframes

M15-H1

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
Show Sessionsboolfalseshow_session
Show Bias Reasoningboolfalsebias_reason
US SeasonstringWinter/Springseason
Structure typestringChoch with IDMstructure_type
Mark H/LboolfalseshowHL
High/Lowcolorcolor.rgb(0, 0, 0, 8)colorHL
Show Volume on Major HLbooltrueshowVolMajor
Mark CirclebooltrueshowCircleHL
Bullishcolorcolor.rgb(76, 175, 79, 8)bull
Bearishcolorcolor.rgb(255, 82, 82, 8)bear
Show Internal StructurebooltrueshowMn
SizestringautointernalStructureType
Show all 40 settings
SettingTypeDefaultVariable
High pivotscolor#f57f17puUpco
Low pivotscolor#006064puDnco
Show Volume on Gann wavesbooltrueshowVolGann
Show B O SbooltrueshowBOS
Show CHoCHbooltrueshowChoChStructure
Show IDMbooltrueshowIDM
Show PDHbooltrueshowPdh
lengPdhint19lengPdh
Show PDLbooltrueshowPdl
lengPdlint19lengPdl
Show EquilibriumboolfalseshowMid
lengMidint19lengMid
Show H/L sweeping linesbooltrueshowSw
Mark "X"booltruemarkX
Sweeping Colorcolorcolor.purplecolorSweep
Show Target profitbooltrueshowTP
Target Colorcolor#b2b5becolorTP
Show live BOSbooltrueshowliveBOS
lengBosint11lengBos
Show live ChoChbooltrueshowliveChoch
lengChochint11lengChoch
Show live IDMbooltrueshowliveIDM
lengIDMint11lengIDM
IDM Colorcolorcolor.rgb(68, 68, 68, 8)colorIDM
Show SCOBbooltrueshowSCOB
Bullish SCOBcolorcolor.aquascobUp
Bearish SCOBcolorcolor.fuchsiascobDn
Show ISBbooltrueshowISB
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

ICT Equal Highs and Lows

by ArunKBhaskar
575 stars

This overlay indicator finds pivot highs and lows on the price chart and compares the last two highs and the last two lows. When the percentage difference between a pair is below the Equal HL threshold, it treats them as equal levels and draws a box. Left and

Indicator v5 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Equal Highs and Lows

DE-tracker

by Allen1212
303 stars

This indicator plots labels on the TradingView price chart to mark ARK Invest's buy and sell trades for the DE stock. Buy transactions appear in green with X1 or X2 multipliers showing the number of ARK ETFs involved on the same day; sells appear in red. The m

Indicator v4 Support & resistanceSMC / ICT / price action
Updated 2 years ago · None stated GitHub repository of DE-tracker

This Pine Script indicator plots a ZigZag on the price chart. It identifies reversal points when price moves more than a set percentage from the last pivot. The script calculates forecasts using zigzag data and labels detected geometric patterns including harm

Indicator v4 TrendSupport & resistance

Golden Bullet Strategy

by TradersPost
218 stars

The Golden Bullet Strategy is an overlay signal indicator based on Smart Money Concepts and time-based liquidity models. It identifies liquidity sweeps of a designated reference session range during a specified hunt window. It then confirms Market Structure Sh

Indicator v6 SMC / ICT / price actionSessions & time
Updated 3 months ago · None stated GitHub repository of Golden Bullet Strategy

Open Source Fractal

by cantolab
183 stars

This indicator implements the Fractal Model concept to plot market structure shifts, liquidity sweeps, and Change in State of Delivery (CISD) levels. It overlays custom higher-timeframe candle previews, open prices, and time markers directly on the price chart

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · MIT GitHub repository of Open Source Fractal

Swing Failure Signals

by Ganador1
154 stars

This indicator calculates pivot highs and pivot lows to detect liquidity sweeps and Swing Failure Patterns. It identifies when price breaks past recent key swing highs or lows within a configurable historical window, then looks for a quick reversal or change i

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 2 months ago · Apache-2.0 GitHub repository of Swing Failure Signals

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers