Indicator · Pine Script v5 · On the chart

Pivot Waves + 8-Pattern + CHoCH

by gnuhhung317

This indicator identifies high and low pivot points on the price chart using user-defined left and right bars around each bar.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code631
Settings20inputs
LicenceMIT
Last update11 months ago12 Nov 2025

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

631 lines of Pine Script v5 from indicator.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

indicator.pine
//@version=5
indicator("Pivot Waves + 8-Pattern + CHoCH (upfate ver.)", overlay=true, max_lines_count=500, max_labels_count=500)

// ========== Inputs ==========
left        = input.int(1,  "Pivot Left",  minval=1)
right       = input.int(1,  "Pivot Right", minval=1)
drawWaves   = input.bool(true, "Vẽ sóng (nối line giữa các pivot)")
useBodyBreak= input.bool(true, "CHoCH theo thân nến (close)")
touchTicks  = input.int(2, "Độ dung sai 'chạm' (số tick)", minval=0)
keepPivots  = input.int(200, "Số pivot tối đa lưu", minval=20)
plotPvtMark = input.bool(true, "Hiển thị PH/PL")
plotEightLbl = input.bool(true, "Ghi nhãn P1..P8 gần nhất")
showTable   = input.bool(true, "Bảng thông tin")

// --- Variant filters (BẮT BUỘC - chỉ detect pivot có variant hợp lệ)
allowPH1 = input.bool(true,  "Cho phép PH loại 1")
allowPH2 = input.bool(true,  "Cho phép PH loại 2 (con là nến #1)")
allowPH3 = input.bool(true,  "Cho phép PH loại 3 (biến thể khác)")
allowPH4 = input.bool(true,  "Cho phép PH loại 4")
allowPH5 = input.bool(true,  "Cho phép PH loại 5")
allowPL1 = input.bool(true,  "Cho phép PL loại 1")
allowPL2 = input.bool(true,  "Cho phép PL loại 2 (con là nến #1)")
allowPL3 = input.bool(true,  "Cho phép PL loại 3 (biến thể khác)")
allowPL4 = input.bool(true,  "Cho phép PL loại 4")
allowPL5 = input.bool(true,  "Cho phép PL loại 5")
showVariantLabel = input.bool(false, "Hiển thị mã biến thể trên chart")

var bool chochLocked = false 

// Store CHoCH bar for persistent label
var int chochUpBar = na
var int chochDownBar = na 

// ========== Storage ==========
var prices = array.new_float()
var bars   = array.new_int()
var highs  = array.new_bool()
var volumes = array.new_float()  // Store pivot volumes

storePivot(_bar, _price, _isHigh) =>
    array.push(bars,   _bar)
    array.push(prices, _price)
    array.push(highs,  _isHigh)
    // Store volume at pivot bar
    off = math.max(0, bar_index - _bar)
    array.push(volumes, volume[off])
    while array.size(bars) > keepPivots
        array.shift(bars)
        array.shift(prices)
        array.shift(highs)
        array.shift(volumes)

pCount() => array.size(prices)

getPivotFromEnd(idxFromEnd) =>
    sz = array.size(prices)
    [ array.get(bars,   sz-1-idxFromEnd),
      array.get(prices, sz-1-idxFromEnd),
      array.get(highs,  sz-1-idxFromEnd) ]

getVolumeFromEnd(idxFromEnd) =>
    sz = array.size(volumes)
    array.get(volumes, sz-1-idxFromEnd)

// Dung sai tick
tol = syminfo.mintick * touchTicks

// ========== Helpers cho phân loại biến thể ==========
getHLTripletAroundMid() =>
    // Timeline: [LEFT/h3] → [CENTER/h2/PIVOT] → [RIGHT/h1]
    // Pine indexing: [right+1] → [right] → [right-1]
    float h2 = high[right]      // CENTER (pivot bar)
    float l2 = low[right]
    float h3 = na               // LEFT side
    float l3 = na
    float h1 = na               // RIGHT side
    float l1 = na
    
    if bar_index > right + 1
        h3 := high[right + 1]   // LEFT side
        l3 := low[right + 1]
    if right - 1 >= 0
        h1 := high[right - 1]   // RIGHT side
        l1 := low[right - 1]
    [h1,h2,h3,l1,l2,l3]

isPH_type1() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 > h1 and h2 > h3) and (l2 > l1 and l2 > l3)

isPH_type2() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 >= h1 and h2 > h3) and (l2 > l3 and l2 < l1)

isPH_type3() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 > h1 and h2 >= h3) and (l2 < l3 and l2 > l1)

isPH_type4() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 >= h3 and h2 > h1) and (l2 <= l3 and l2 > l1)

isPH_type5() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 >= h3 and h2 >= h1) and (l2 <= l3 and l2 > l1)

isPL_type1() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (l2 < l1 and l2 < l3) and (h2 < h1 and h2 < h3)

isPL_type2() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 >= h1 and h2 < h3) and (l2 < l3 and l2 <= l1)

isPL_type3() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (l2 < l1 and l2 < l3) and (h2 < h1 and h2 >= h3)

isPL_type4() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 <= h3 and h2 < h1) and (l2 >= l3 and l2 <= l1)

isPL_type5() =>
    [h1,h2,h3,l1,l2,l3] = getHLTripletAroundMid()
    not na(h1) and not na(h3) and (h2 >= h3 and h2 < h1) and (l2 <= l3 and l2 <= l1)

classifyPivotVariant(isHighPivot) =>
    var string t = "NA"
    if isHighPivot
        t := isPH_type1() ? "PH1" :
             isPH_type2() ? "PH2" :
             isPH_type3() ? "PH3" :
             isPH_type4() ? "PH4" :
             isPH_type5() ? "PH5" : "NA"
    else
        t := isPL_type1() ? "PL1" :
             isPL_type2() ? "PL2" :
             isPL_type3() ? "PL3" :
             isPL_type4() ? "PL4" :
             isPL_type5() ? "PL5" : "NA"
    t

allowedByUser(t) =>
    switch t
        "PH1" => allowPH1
        "PH2" => allowPH2
        "PH3" => allowPH3
        "PH4" => allowPH4
        "PH5" => allowPH5
        "PL1" => allowPL1
        "PL2" => allowPL2
        "PL3" => allowPL3
        "PL4" => allowPL4
        "PL5" => allowPL5
        => false

// ========== Custom Pivot Detection (không dùng ta.pivot*) ==========
// Detect pivot manually based on variant classification only
var bool isPH = false
var bool isPL = false
var float newPivotPrice = na
var int newPivotBar = na
var bool newPivotHigh = false

// Reset detection flags
isPH := false
isPL := false
newPivotPrice := na
newPivotBar := na

// Chỉ check khi có đủ bars 2 bên
if bar_index > right + 1 and right - 1 >= 0
    // Check Pivot High variants
    if isPH_type1() and allowPH1
        isPH := true
    else if isPH_type2() and allowPH2
        isPH := true
    else if isPH_type3() and allowPH3
        isPH := true
    else if isPH_type4() and allowPH4
        isPH := true
    else if isPH_type5() and allowPH5
        isPH := true
    
    // Check Pivot Low variants
    if isPL_type1() and allowPL1
        isPL := true
    else if isPL_type2() and allowPL2
        isPL := true
    else if isPL_type3() and allowPL3
        isPL := true
    else if isPL_type4() and allowPL4
        isPL := true
    else if isPL_type5() and allowPL5
        isPL := true
    
    // Set pivot data if detected
    if isPH
        newPivotPrice := high[right]
        newPivotBar := bar_index - right
        newPivotHigh := true
    else if isPL
        newPivotPrice := low[right]
        newPivotBar := bar_index - right
        newPivotHigh := false

// ========== State vars ==========
var int   lastPivotBar   = na
var float lastPivotPrice = na
var bool  lastEightUp    = false
var bool  lastEightDown  = false
var int   lastEightBarIdx = na
var float p2_price       = na
var bool  acceptedPH_event = false
var bool  acceptedPL_event = false

// Pattern group order flags (needed for CHoCH confirmation)
var bool g1_upOrder = false
var bool g1_downOrder = false
var bool g2_upOrder = false
var bool g2_downOrder = false
var bool g3_upOrder = false
var bool g3_downOrder = false
// Additional groups G4, G5, G6 removed - only G1..G3 are used now

// ========== Khi có pivot mới ==========
acceptedPH_event := false
acceptedPL_event := false

if (isPH or isPL)
    // ========== BẮT BUỘC lọc theo variant - KHÔNG detect pivot thuần ==========
    tvar = classifyPivotVariant(isPH)
    accept = (tvar != "NA") and allowedByUser(tvar)

    if accept
        // Check if we need to replace the last pivot (if same type and new one is better)
        shouldReplaceLast = false
        shouldSkipNew = false  // Flag to skip adding new pivot if old one is better
        
        if pCount() > 0
            [lastBar, lastPrice, lastHigh] = getPivotFromEnd(0)
            newHigh = isPH
            
            // If last pivot is same type, check if new one is better
            if lastHigh == newHigh
                gap = newPivotBar - lastBar - 1
                
                // If consecutive (gap = 0, truly adjacent), choose the better one
                if gap == 0
                    if newHigh
                        // Both are PH → keep the higher one
                        if newPivotPrice > lastPrice
                            shouldReplaceLast := true  // New is better → replace old
                        else
                            shouldSkipNew := true       // Old is better → skip new
                    else
                        // Both are PL → keep the lower one
                        if newPivotPrice < lastPrice
                            shouldReplaceLast := true  // New is better → replace old
                        else
                            shouldSkipNew := true       // Old is better → skip new
        
        // Replace last pivot if new one is better
        if shouldReplaceLast
            // Remove last pivot
            array.pop(bars)
            array.pop(prices)
            array.pop(highs)
            array.pop(volumes)
            
            // Store new pivot (will replace the removed one)
            storePivot(newPivotBar, newPivotPrice, newPivotHigh)
            if drawWaves and pCount() > 1
                // Redraw wave from previous pivot
                [prevBar, prevPrice, _] = getPivotFromEnd(1)
                line.new(prevBar, prevPrice, newPivotBar, newPivotPrice, color=color.new(color.blue, 0), width=2)
            lastPivotBar   := newPivotBar
            lastPivotPrice := newPivotPrice
        else if not shouldSkipNew
            // Normal case: insert fake pivot if needed, then add new pivot
            if pCount() > 0
                [lastBar, lastPrice, lastHigh] = getPivotFromEnd(0)
                newHigh = isPH
                if lastHigh == newHigh
                    gap = newPivotBar - lastBar - 1
                    if gap > 0
                        firstBarInGap = lastBar + 1
                        lastBarInGap  = newPivotBar - 1
                        offsetFirst   = bar_index - firstBarInGap
                        offsetLast    = bar_index - lastBarInGap
                        if offsetFirst >= 0 and offsetLast >= 0
                            float insertPrice = newHigh ? low[offsetFirst] : high[offsetFirst]
                            int   insertBar   = firstBarInGap
                            bool  insertHigh  = not newHigh
                            for i = offsetFirst to offsetLast
                                float candidate = newHigh ? low[i] : high[i]
                                if (newHigh and candidate < insertPrice) or (not newHigh and candidate > insertPrice)
                                    insertPrice := candidate
                                    insertBar   := bar_index - i
                            if insertBar > lastBar and insertBar < newPivotBar
                                storePivot(insertBar, insertPrice, insertHigh)
                                if drawWaves and not na(lastPivotBar)
                                    line.new(lastPivotBar, lastPivotPrice, insertBar, insertPrice, color=color.new(color.blue, 0), width=2)
                                lastPivotBar   := insertBar
                                lastPivotPrice := insertPrice

            // Lưu pivot mới
            storePivot(newPivotBar, newPivotPrice, newPivotHigh)
            if drawWaves and not na(lastPivotBar)
                line.new(lastPivotBar, lastPivotPrice, newPivotBar, newPivotPrice, color=color.new(color.blue, 0), width=2)
            lastPivotBar   := newPivotBar
            lastPivotPrice := newPivotPrice

        // reset khóa CHoCH khi đã hình thành pivot sau cụm 8 trước đó
        if not na(lastEightBarIdx) and newPivotBar > lastEightBarIdx
            chochLocked := false

        acceptedPH_event := isPH
        acceptedPL_event := isPL

        if showVariantLabel and tvar != "NA"
            label.new(newPivotBar, newPivotPrice, tvar, style=label.style_label_center,
                      textcolor=color.white, color=color.new(color.black, 10), size=size.tiny)

// ========== 8-Pattern ==========
var float pivot6 = na   // p6 (reference cho CHoCH)
var float pivot5 = na   // p5 (additional reference cho CHoCH)
var float pivot4 = na   // p4 (additional reference cho CHoCH)
var float saved_p5 = na // Store p5 for pattern group confirmation
var float saved_p7 = na // Store p7 for pattern group confirmation (G2)
eightReady = pCount() >= 8
if eightReady
    [b8,p8,h8] = getPivotFromEnd(0)
    [b7,p7,h7] = getPivotFromEnd(1)
    [b6,p6,h6] = getPivotFromEnd(2)
    [b5,p5,h5] = getPivotFromEnd(3)
    [b4,p4,h4] = getPivotFromEnd(4)
    [b3,p3,h3] = getPivotFromEnd(5)
    [b2,p2,h2] = getPivotFromEnd(6)
    [b1,p1,h1] = getPivotFromEnd(7)

    // cấu trúc xen kẽ 8 pivot
    upStruct   = (not h1) and h2 and (not h3) and h4 and (not h5) and h6 and (not h7) and h8
    downStruct = h1 and (not h2) and h3 and (not h4) and h5 and (not h6) and h7 and (not h8)

    // Retest P7 ↔ P4 (thay vì P5 ↔ P2)
    off7 = math.max(0, bar_index - b7)
    off4 = math.max(0, bar_index - b4)
    hi7 = high[off7]
    lo7 = low[off7]
    hi4 = high[off4]
    lo4 = low[off4]
    touchRetest = (upStruct and (lo7 < hi4)) or (downStruct and (hi7 > lo4))

    // P8 là cực trị trong cụm 1–8
    highsArr  = array.from(p1, p2, p3, p4, p5, p6, p7, p8)
    lowsArr   = array.from(p1, p2, p3, p4, p5, p6, p7, p8)
    isHighest8 = p8 == array.max(highsArr)
    isLowest8  = p8 == array.min(lowsArr)

    // Ràng buộc thứ tự 8-pivot - 3 nhóm (G1, G2, G3)
    // Ràng buộc thứ tự - nhóm (G1..G3)
    // We intentionally removed the 10-pivot groups (G4/G5/G6). Always use the most recent 8 pivots (p1..p8).

    // G1 (Original):
    g1_upOrder   := (p2 < p4 and p4 < p6 and p6 < p8) and (p3 < p5 and p5 < p7)
    g1_downOrder := (p3 > p5 and p5 > p7) and (p2 > p4 and p4 > p6 and p6 > p8)

    // G2:
    // Uptrend: p3 < p7 < p5, p2 < p6 < p4 < p8, p2 < p5
    g2_upOrder   := (p3 < p7 and p7 < p5) and (p2 < p6 and p6 < p4 and p4 < p8) and (p2 < p5)
    // Downtrend: p3 > p7 > p5, p2 > p6 > p4 > p8, p2 > p5
    g2_downOrder := (p3 > p7 and p7 > p5) and (p2 > p6 and p6 > p4 and p4 > p8) and (p2 > p5)

    // G3:
    // Uptrend: p3 < p5 < p7, p2 < p6 < p4 < p8, p2 < p5
    g3_upOrder   := (p3 < p5 and p5 < p7) and (p2 < p6 and p6 < p4 and p4 < p8) and (p2 < p5)
    // Downtrend: p3 > p5 > p7, p2 > p6 > p4 > p8, p2 > p5
    g3_downOrder := (p3 > p5 and p5 > p7) and (p2 > p6 and p6 > p4 and p4 > p8) and (p2 > p5)

    // Combined order check (any valid group) - only G1..G3 are considered
    upOrderOK   = g1_upOrder or g2_upOrder or g3_upOrder
    downOrderOK = g1_downOrder or g2_downOrder or g3_downOrder

    // Điều kiện breakout mới
    off5 = math.max(0, bar_index - b5)
    off3 = math.max(0, bar_index - b3)
    off2 = math.max(0, bar_index - b2)
    off1 = math.max(0, bar_index - b1)
    
    lo5 = low[off5]
    hi2 = high[off2]
    lo3 = low[off3]
    lo1 = low[off1]
    hi5 = high[off5]
    lo2 = low[off2]
    hi3 = high[off3]
    hi1 = high[off1]
    
    upBreakout   = (lo5 > hi2) 
    downBreakout = (hi5 < lo2) 

    // Mẫu hợp lệ với điều kiện breakout phù hợp
    // Uptrend pattern + UP breakout conditions
    lastEightUp   := upStruct   and upOrderOK   and touchRetest and isHighest8 and upBreakout
    // Downtrend pattern + DOWN breakout conditions
    lastEightDown := downStruct and downOrderOK and touchRetest and isLowest8  and downBreakout

    // Lưu pivots khi mẫu hợp lệ
    if (lastEightDown or lastEightUp)
        pivot5 := p5
        pivot6 := p6
        pivot4 := p4
        // Store pivot values for confirmation logic
        saved_p5 := p5
        saved_p7 := p7
        // saved_p8 removed (G4/G5/G6 groups removed)

    lastEightBarIdx := b8
    p2_price        := p2

// ---- Sau khi đã có eightReady ----
isAfterEight = eightReady and (bar_index > nz(lastEightBarIdx))

var bool fireChochUp = false
var bool fireChochDn = false
var string matchedGroup = na  // Track which pattern group matched

if eightReady and not na(pivot6) and not na(pivot5) and not na(pivot4)
    // ========== 3-CANDLE CHoCH CONFIRMATION (matching Python logic) ==========
    // Pre-CHoCH [2] → CHoCH Bar [1] → Confirmation [0]
    
    // CHoCH Bar conditions (nến [1] - previous bar)
    // CHoCH Up: low[1] > low[2] AND close[1] > high[2] AND close[1] > pivot6 AND close[1] < pivot2 AND close[1] > pivot4
    chochUpBar = (low[1] > low[2]) and (close[1] > high[2]) and 
                 (close[1] > pivot6) and (close[1] < p2_price) and (close[1] > pivot4)
    
    // CHoCH Down: high[1] < high[2] AND close[1] < low[2] AND close[1] < pivot6 AND close[1] > pivot2 AND close[1] < pivot4
    chochDownBar = (high[1] < high[2]) and (close[1] < low[2]) and 
                   (close[1] < pivot6) and (close[1] > p2_price) and (close[1] < pivot4)

    // Confirmation conditions - Basic (nến [0] - current bar)
    // CHoCH Up confirmed: close của confirmation > high của nến pre-CHoCH [2]
    confirmUpBasic = (close > high[2])
    
    // CHoCH Down confirmed: close của confirmation < low của nến pre-CHoCH [2]
    confirmDownBasic = (close < low[2])

    // Check nếu nến CHoCH (bar[1]) là pivot mới nhất → lấy pivot trước đó
    // để tránh fail điều kiện isAfterEight
    chochBarIsPivot = (bar_index - 1 == lastEightBarIdx)
    nineReady = pCount() >= 9
    var int effectiveEightBarIdx = na
    var bool effectiveLastEightUp = false
    var bool effectiveLastEightDown = false
    
    if chochBarIsPivot and nineReady
        // Nến CHoCH là pivot → lấy pivot trước đó (offset 1)
        [b9,_,_] = getPivotFromEnd(1)
        effectiveEightBarIdx := b9
        // Lấy giá trị lastEightUp/Down từ nến trước đó (bar[1])
        effectiveLastEightUp := lastEightUp[1]
        effectiveLastEightDown := lastEightDown[1]
    else
        // Nến CHoCH không là pivot → dùng lastEightBarIdx bình thường
        effectiveEightBarIdx := lastEightBarIdx
        effectiveLastEightUp := lastEightUp
        effectiveLastEightDown := lastEightDown
    
    // Recalculate isAfterEight với effectiveEightBarIdx
    isAfterEightSafe = eightReady and (bar_index > nz(effectiveEightBarIdx))

    // Match with pattern direction - sử dụng isAfterEightSafe và effective values
    baseUp   = isAfterEightSafe and effectiveLastEightDown and chochUpBar and confirmUpBasic
    baseDown = isAfterEightSafe and effectiveLastEightUp   and chochDownBar and confirmDownBasic

    // ========== PATTERN GROUP SPECIFIC CONDITIONS for Confirmation Candle ==========
    // Determine which group matched
    if lastEightUp
        if g1_upOrder
            matchedGroup := "G1"
        else if g2_upOrder
            matchedGroup := "G2"
        else if g3_upOrder
            matchedGroup := "G3"
        else
            matchedGroup := na
    else if lastEightDown
        if g1_downOrder
            matchedGroup := "G1"
        else if g2_downOrder
            matchedGroup := "G2"
        else if g3_downOrder
            matchedGroup := "G3"
        else
            matchedGroup := na

    // Get volumes at pivot bars
    vol8 = getVolumeFromEnd(0)
    vol7 = getVolumeFromEnd(1)
    vol6 = getVolumeFromEnd(2)
    vol5 = getVolumeFromEnd(3)
    vol4 = getVolumeFromEnd(4)
    vol_choch = volume[1]  // Volume of CHoCH candle (previous bar)
    chochBarIsPivotVol = (bar_index - 1 == lastEightBarIdx)
    
    if chochBarIsPivotVol and pCount() >= 9
        // CHoCH bar is p8 → shift indices by 1
        vol8 := getVolumeFromEnd(1)  // p9 → p8
        vol7 := getVolumeFromEnd(2)  // p8 → p7
        vol6 := getVolumeFromEnd(3)  // p7 → p6
        vol5 := getVolumeFromEnd(4)  // p6 → p5
        vol4 := getVolumeFromEnd(5)  // p5 → p4

    
    // label.new(bar_index, high,   str.tostring(volume[0]),style=label.style_label_down, textcolor=color.white,color=color.new(color.orange, 0), size=size.tiny)

    // Apply pattern group conditions with volume
    confirmUp = false
    confirmDown = false

    if baseUp and not na(matchedGroup)
        // Downtrend → CHoCH Up
        priceCondition = false
        volumeCondition = false

        if matchedGroup == "G1"
            // G1: Close_CF <= HIGH_5
            priceCondition := close <= saved_p5
            
            // Volume condition G1: 678_ok AND 456_ok
            // 1. (Vol8 OR Vol6 OR Vol_CHoCH) là lớn nhất cụm 678
            max_678 = math.max(vol6, math.max(vol7, vol8))
            vol_678_ok = (vol8 == max_678) or (vol6 == max_678) or (vol_choch >= max_678)
            
            // 2. (Vol4 OR Vol6) là lớn nhất cụm 456
            max_456 = math.max(vol4, math.max(vol5, vol6))
            vol_456_ok = (vol4 == max_456) or (vol6 == max_456)

            volumeCondition := vol_678_ok and vol_456_ok
            confirmUp := priceCondition and volumeCondition
            
        else if matchedGroup == "G2"
            // G2: Close_CF <= HIGH_7
            priceCondition := close <= saved_p7
            
            // Volume condition G2/G3:
            // (Vol4 OR Vol8 OR Vol_CHoCH) là lớn nhất cụm 45678
            max_45678 = math.max(vol4, math.max(vol5, math.max(vol6, math.max(vol7, vol8))))
            volumeCondition := (vol4 == max_45678) or (vol8 == max_45678) or (vol_choch >= max_45678)
            confirmUp := priceCondition and volumeCondition
            
        else if matchedGroup == "G3"
            // G3: Close_CF <= HIGH_5
            priceCondition := close <= saved_p5
            
            // Volume condition G2/G3:
            // (Vol4 OR Vol8 OR Vol_CHoCH) là lớn nhất cụm 45678
            max_45678 = math.max(vol4, math.max(vol5, math.max(vol6, math.max(vol7, vol8))))
            volumeCondition := (vol4 == max_45678) or (vol8 == max_45678) or (vol_choch >= max_45678)
            confirmUp := priceCondition and volumeCondition

        // Additional groups for Up confirmation (Downtrend -> CHoCH Up): G4', G5', G6'
        // Groups G4/G5/G6 removed - no additional group-specific Up rules
    
    if baseDown and not na(matchedGroup)
        // Uptrend → CHoCH Down
        priceCondition = false
        volumeCondition = false
        
        if matchedGroup == "G1"
            // G1: Close_CF >= LOW_5
            priceCondition := close >= saved_p5
            
            // Volume condition G1: 678_ok AND 456_ok
            // 1. (Vol8 OR Vol6 OR Vol_CHoCH) là lớn nhất cụm 678
            max_678 = math.max(vol6, math.max(vol7, vol8))
            vol_678_ok = (vol8 == max_678) or (vol6 == max_678) or (vol_choch >= max_678)
            
            // 2. (Vol4 OR Vol6) là lớn nhất cụm 456
            max_456 = math.max(vol4, math.max(vol5, vol6))
            vol_456_ok = (vol4 == max_456) or (vol6 == max_456)
            
            volumeCondition := vol_678_ok and vol_456_ok
            confirmDown := priceCondition and volumeCondition
            
        else if matchedGroup == "G2"
            // G2: Close_CF >= LOW_7
            priceCondition := close >= saved_p7
            
            // Volume condition G2/G3:
            // (Vol4 OR Vol8 OR Vol_CHoCH) là lớn nhất cụm 45678
            max_45678 = math.max(vol4, math.max(vol5, math.max(vol6, math.max(vol7, vol8))))
            volumeCondition := (vol4 == max_45678) or (vol8 == max_45678) or (vol_choch >= max_45678)
            confirmDown := priceCondition and volumeCondition
            
        else if matchedGroup == "G3"
            // G3: Close_CF >= LOW_5
            priceCondition := close >= saved_p5
            
            // Volume condition G2/G3:
            // (Vol4 OR Vol8 OR Vol_CHoCH) là lớn nhất cụm 45678
            max_45678 = math.max(vol4, math.max(vol5, math.max(vol6, math.max(vol7, vol8))))
            volumeCondition := (vol4 == max_45678) or (vol8 == max_45678) or (vol_choch >= max_45678)
            confirmDown := priceCondition and volumeCondition
 
        // New groups for Down confirmation (Uptrend -> CHoCH Down)
        // Groups G4/G5/G6 removed - no additional group-specific Down rules

    // Fire signal với locking
    fireChochUp := (not chochLocked) and confirmUp
    fireChochDn := (not chochLocked) and confirmDown

    if fireChochUp or fireChochDn
        chochLocked := true
        // Store bar index for persistent label
        if fireChochUp
            chochUpBar := bar_index
        if fireChochDn
            chochDownBar := bar_index

// ========== Plot ==========
plotshape(fireChochUp,  title="CHoCH Up",   style=shape.labelup,   text="CH↑",
          color=color.new(color.teal, 0),   textcolor=color.white, location=location.belowbar, size=size.tiny)
plotshape(fireChochDn,  title="CHoCH Down", style=shape.labeldown, text="CH↓",
          color=color.new(color.orange, 0), textcolor=color.white, location=location.abovebar, size=size.tiny)

// ========== Alert Conditions ==========
alertcondition(fireChochUp, title="CHoCH Up Alert", message="CHoCH Up detected on {{ticker}} {{interval}}")
alertcondition(fireChochDn, title="CHoCH Down Alert", message="CHoCH Down detected on {{ticker}} {{interval}}")
alertcondition(fireChochUp or fireChochDn, title="CHoCH Alert (Any)", message="CHoCH detected on {{ticker}} {{interval}}")

Overview

Pivot Waves + 8-Pattern + CHoCH connects pivot points with wave lines, plots PH and PL marks, and labels the latest P1 to P8 patterns. A table shows pivot details including volumes. The script detects CHoCH signals based on body breaks or candle closes, with options to enable or disable each of five pattern variants. All elements draw directly on the price chart with customizable persistence of up to 200 pivots and tick-based touch tolerance.

Details

Published by

gnuhhung317 on GitHub

Draws

On the price chart

Markets

Any symbol

Timeframes

Any timeframe

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
Pivot Leftint1left
Pivot Rightint1right
Vẽ sóng (nối line giữa các pivot)booltruedrawWaves
CHoCH theo thân nến (close)booltrueuseBodyBreak
Độ dung sai 'chạm' (số tick)int2touchTicks
Số pivot tối đa lưuint200keepPivots
Hiển thị PH/PLbooltrueplotPvtMark
Ghi nhãn P1..P8 gần nhấtbooltrueplotEightLbl
Bảng thông tinbooltrueshowTable
Cho phép PH loại 1booltrueallowPH1
Cho phép PH loại 2 (con là nến #1)booltrueallowPH2
Cho phép PH loại 3 (biến thể khác)booltrueallowPH3
Show all 20 settings
SettingTypeDefaultVariable
Cho phép PH loại 4booltrueallowPH4
Cho phép PH loại 5booltrueallowPH5
Cho phép PL loại 1booltrueallowPL1
Cho phép PL loại 2 (con là nến #1)booltrueallowPL2
Cho phép PL loại 3 (biến thể khác)booltrueallowPL3
Cho phép PL loại 4booltrueallowPL4
Cho phép PL loại 5booltrueallowPL5
Hiển thị mã biến thể trên chartboolfalseshowVariantLabel
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

This overlay indicator detects tweezer tops and bottoms and kangaroo-tail pin bars, then draws supply and demand zones from those patterns. It scores each zone, decays freshness, merges overlapping zones, and can invalidate a zone after a close-through or afte

Indicator v6 Candlestick patternsSupport & resistance
Updated 3 days ago · MIT GitHub repository of Tweezer & Kangaroo Zones [WavesUnchained]
7 stars

This indicator plots Smart Money Concepts and price action components directly on the price chart. It highlights bullish and bearish order blocks, multi-timeframe pivot points, market structure swings with key Fibonacci retracement levels (0.618 and 0.786), zo

Indicator v5 SMC / ICT / price actionSupport & resistance Repaints
Updated 3 months ago · None stated GitHub repository of Cryptokazancev Strategy Pack
3 stars

This Pine Script indicator plots Smart Money Concepts market structure directly on the price chart. It identifies swing highs and lows, internal market structures, Break of Structure events, Change of Character shifts, and EQH/EQL levels. Breaker blocs are hig

Indicator v5 SMC / ICT / price actionCandlestick patterns Repaints
Updated 12 months ago · None stated GitHub repository of SMC & Breaker Blocs [Krishna]

Price Action Toolkit

by TamTH-Dev
2 stars

The Price Action Toolkit is an indicator that combines several price action tools on the chart. It calculates the ATR with user-defined length and shows it in a table. It plots multiple Kaufman Adaptive Moving Averages (KAMA) with different lengths and sources

Indicator v6 TrendSupport & resistance
Updated 6 weeks ago · None stated GitHub repository of Price Action Toolkit

Smart Money Concepts [LuxAlgo]

by srinivas-yarramsetti
2 stars

This indicator plots Smart Money Concepts elements on the price chart including internal and swing order blocks, bullish and bearish structures with BOS and CHoCH breaks, fair value gaps, equal highs and lows, and swing points. It can color candles according t

Indicator v5 SMC / ICT / price actionCandlestick patterns Repaints
Updated 3 years ago · None stated GitHub repository of Smart Money Concepts [LuxAlgo]

j14-E1 adv

by moonyroy
1 stars

This indicator plots ZigZag lines connecting swing highs and lows to map price structure. It draws candle body and shadow boxes on the price chart and detects CHoCH for potential trend shifts and BOS for continuation signals. The main setting toggles candle di

Indicator v6 SMC / ICT / price actionSupport & resistance Repaints
Updated 6 days ago · None stated GitHub repository of j14-E1 adv

More Support & resistance scripts

Related on ForexR

Trade Your Charts With a Regulated Broker

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

Compare brokers