Indicator · Pine Script v6 · On the chart

JCO Swings Trend HTF

by jcornierfra· code credited to jcornier

This Pine Script indicator detects swing highs and lows on a higher timeframe while analyzing the current chart timeframe.

View on GitHub

GitHub stars3on the repository
Pine versionv6current
Lines of code632
Settings9inputs
LicenceNone statedfile: MPL-2.0
Last update11 days ago17 Oct 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

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

Indicator_JCO_Swings_Trend_HTF.pine
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © jcornier
//
// Version: 1.6
// GitHub: https://github.com/jcornierfra/TradingView_Indicator_JCO_Swings_Trend_HTF
//
// Description: Detects swing highs and lows on a higher timeframe (HTF) and determines
//              the trend direction based on swing structure. Features include:
//              - Trend detection: Bullish/Bearish with Momentum or Compression status
//              - CHoCH (Change of Character) detection for potential reversals
//              - Liquidity sweep detection with close price confirmation
//              - Swing alternation with automatic missing swing insertion
//              - Optional CHoCH line drawing on the broken swing level
//
// Changelog:
//   v1.0 - 2026-02-05: Initial release
//   v1.1 - 2026-02-06: CHoCH detection with previous trend analysis (swings 1,2,3)
//                       and 5-candle close confirmation window
//   v1.2 - 2026-02-09: Gate trend change (require CHoCH confirmation for reversals),
//                       "Unclear" displayed in gray
//   v1.3 - 2026-02-10: Dual CHoCH detection with liquidity sweep restoration
//                       (aligned with cTrader v1.7)
//   v1.4 - 2026-02-10: Configurable pip value parameter (replaces hardcoded mintick * 100)
//   v1.5 - 2026-02-18: Close-based trend calculation: calcSwingsTrend uses pivot close prices
//                       instead of high/low to filter liquidity wicks (CHoCH, liquidity sweep,
//                       expansion remain on high/low)
//                       Pivot close simplified: uses close of the pivot candle itself instead
//                       of best close in 5-candle window (fixes false CHoCH signals)
//   v1.6 - 2026-03-28: Optional CHoCH line drawn from SH1/SL1 to the breaking swing
//                       ("Draw CHoCH Line" input, lime = bullish, red = bearish)

//@version=6
indicator("JCO Swings Trend HTF", overlay=true, max_labels_count=500, max_lines_count=500)

// ----- Inputs -----
swingPeriod = input.int(5, "Swing Period", minval=3, group="Swings H & L")
swingTimeFrame = input.timeframe("60", "Swing Time Frame", group="Swings H & L")

drawSwingIcons = input.bool(true, "Draw Icons", group="Swings Chart")
drawChochLine = input.bool(true, "Draw CHoCH Line", group="Swings Chart")
swingIconGapPercent = input.float(0.1, "Icon Gap (% of price)", minval=0.01, maxval=1.0, step=0.01, group="Swings Chart")
displayDashboard = input.bool(true, "Display Dashboard", group="Swings Chart")
displaySwingValues = input.bool(false, "Show SH/SL Values", group="Swings Chart")
pipValue = input.float(0.1, "Pip Value ($)", minval=0.0001, step=0.01, tooltip="Value of 1 pip in price units. Gold: 0.1, Forex majors: 0.0001", group="Swings Chart")
dashboardBorderWidth = input.int(0, "Dashboard Border Width", minval=0, maxval=3, group="Swings Chart")

// ----- Constants -----
int NODIRECTION = 0
int BULLISH = 1
int BEARISH = -1
int MOMENTUM = 1
int COMPRESSION = 2

// CHoCH (Change of Character) constants
int CONTINUATION = 0
int CHOCH_BULLISH = 1
int CHOCH_BEARISH = -1

// ----- Check if chart timeframe is higher than swing timeframe -----
int chartTFSeconds = timeframe.in_seconds(timeframe.period)
int swingTFSeconds = timeframe.in_seconds(swingTimeFrame)
bool validTimeframe = chartTFSeconds <= swingTFSeconds

// ----- Calculate pivot offset -----
int leftBars = swingPeriod / 2
int rightBars = swingPeriod / 2

// ----- Helper function to get pivots on HTF -----
getPivotsHTF() =>
    float pivotHighPrice = ta.pivothigh(leftBars, rightBars)
    float pivotLowPrice = ta.pivotlow(leftBars, rightBars)
    bool isPivotHigh = not na(pivotHighPrice)
    bool isPivotLow = not na(pivotLowPrice)

    float ph0 = ta.valuewhen(isPivotHigh, pivotHighPrice, 0)
    float pl0 = ta.valuewhen(isPivotLow, pivotLowPrice, 0)

    // Close price of the pivot candle itself (for CHoCH detection)
    float phClose0 = ta.valuewhen(isPivotHigh, close[rightBars], 0)
    float plClose0 = ta.valuewhen(isPivotLow, close[rightBars], 0)

    [ph0, pl0, phClose0, plClose0]

// ----- Request data from HTF -----
[rawPH0, rawPL0, rawPHClose, rawPLClose] = request.security(syminfo.tickerid, swingTimeFrame, getPivotsHTF())

// ----- Helper functions for swing bar detection -----
int htfBarsLookback = math.max(50, int(swingTFSeconds / chartTFSeconds) * (rightBars + 3))

// Find bar index where high matches the swing price
findSwingHighBar(float price, int lookback) =>
    int foundBar = na
    for i = 0 to lookback
        if high[i] == price
            foundBar := bar_index - i
            break
    foundBar

// Find bar index where low matches the swing price
findSwingLowBar(float price, int lookback) =>
    int foundBar = na
    for i = 0 to lookback
        if low[i] == price
            foundBar := bar_index - i
            break
    foundBar

// Find the lowest low between two bar indices (exclusive of endpoints)
// Used to find missing swing lows between consecutive swing highs
findLowestLowBetween(int olderBarIdx, int newerBarIdx) =>
    float resultPrice = na
    int resultBar = na
    float resultClose = na
    if not na(olderBarIdx) and not na(newerBarIdx) and newerBarIdx > olderBarIdx + 1
        int startLb = bar_index - olderBarIdx - 1
        int endLb = bar_index - newerBarIdx + 1
        for i = endLb to startLb
            if i >= 0
                if na(resultPrice) or low[i] < resultPrice
                    resultPrice := low[i]
                    resultBar := bar_index - i
                    resultClose := close[i]
    [resultPrice, resultBar, resultClose]

// Find the highest high between two bar indices (exclusive of endpoints)
// Used to find missing swing highs between consecutive swing lows
findHighestHighBetween(int olderBarIdx, int newerBarIdx) =>
    float resultPrice = na
    int resultBar = na
    float resultClose = na
    if not na(olderBarIdx) and not na(newerBarIdx) and newerBarIdx > olderBarIdx + 1
        int startLb = bar_index - olderBarIdx - 1
        int endLb = bar_index - newerBarIdx + 1
        for i = endLb to startLb
            if i >= 0
                if na(resultPrice) or high[i] > resultPrice
                    resultPrice := high[i]
                    resultBar := bar_index - i
                    resultClose := close[i]
    [resultPrice, resultBar, resultClose]

// ----- Apply alternation filter on chart TF -----
// Track last swing type: true = last was HIGH, false = last was LOW
var bool lastWasHigh = false

// Storage for alternating swings (5 swings for liquidity sweep analysis)
var float ph_0 = na, var float ph_1 = na, var float ph_2 = na, var float ph_3 = na, var float ph_4 = na
var float pl_0 = na, var float pl_1 = na, var float pl_2 = na, var float pl_3 = na, var float pl_4 = na
var float lastRawPH = na, var float lastRawPL = na

// Storage for close prices of swing candles (for CHoCH and close-based trend detection)
var float phClose_0 = na, var float phClose_1 = na, var float phClose_2 = na, var float phClose_3 = na
var float plClose_0 = na, var float plClose_1 = na, var float plClose_2 = na, var float plClose_3 = na
var float lastRawPHClose = na, var float lastRawPLClose = na

// Storage for swing labels (to allow moving them when swing is updated)
var label lastSwingHighLabel = na
var label lastSwingLowLabel = na

// Track bar indices of last accepted swings (for finding missing opposite swings)
var int lastAcceptedHighBar = na
var int lastAcceptedLowBar = na

// Detect raw changes (handle na case explicitly for first detection)
bool rawHighChanged = not na(rawPH0) and (na(lastRawPH) or rawPH0 != lastRawPH)
bool rawLowChanged = not na(rawPL0) and (na(lastRawPL) or rawPL0 != lastRawPL)

// Flags for accepted swings
bool newSwingHigh = false
bool newSwingLow = false
// Flags for updated swings (more extreme value found)
bool updateSwingHigh = false
bool updateSwingLow = false

if validTimeframe
    // Both changed - take opposite to last (only update the accepted one's lastRaw)
    if rawHighChanged and rawLowChanged
        if not lastWasHigh
            // Accept high first - shift all values
            ph_4 := ph_3
            ph_3 := ph_2
            ph_2 := ph_1
            ph_1 := ph_0
            ph_0 := rawPH0
            phClose_3 := phClose_2
            phClose_2 := phClose_1
            phClose_1 := phClose_0
            phClose_0 := rawPHClose
            lastWasHigh := true
            newSwingHigh := true
            lastRawPH := rawPH0
            lastRawPHClose := rawPHClose
            lastAcceptedHighBar := findSwingHighBar(rawPH0, htfBarsLookback)
            // Don't update lastRawPL - will be detected next bar
        else
            // Accept low first - shift all values
            pl_4 := pl_3
            pl_3 := pl_2
            pl_2 := pl_1
            pl_1 := pl_0
            pl_0 := rawPL0
            plClose_3 := plClose_2
            plClose_2 := plClose_1
            plClose_1 := plClose_0
            plClose_0 := rawPLClose
            lastWasHigh := false
            newSwingLow := true
            lastRawPL := rawPL0
            lastRawPLClose := rawPLClose
            lastAcceptedLowBar := findSwingLowBar(rawPL0, htfBarsLookback)
            // Don't update lastRawPH - will be detected next bar
    else if rawHighChanged
        if not lastWasHigh
            // Accept high (alternates) - shift all values
            ph_4 := ph_3
            ph_3 := ph_2
            ph_2 := ph_1
            ph_1 := ph_0
            ph_0 := rawPH0
            phClose_3 := phClose_2
            phClose_2 := phClose_1
            phClose_1 := phClose_0
            phClose_0 := rawPHClose
            lastWasHigh := true
            newSwingHigh := true
            lastRawPH := rawPH0
            lastRawPHClose := rawPHClose
            lastAcceptedHighBar := findSwingHighBar(rawPH0, htfBarsLookback)
        else
            // Consecutive high - find missing low between previous high and this one
            int newHighBar = findSwingHighBar(rawPH0, htfBarsLookback)
            bool missingFound = false
            if not na(newHighBar) and not na(lastAcceptedHighBar)
                [mPrice, mBar, mClose] = findLowestLowBetween(lastAcceptedHighBar, newHighBar)
                if not na(mPrice)
                    // Insert missing low
                    pl_4 := pl_3
                    pl_3 := pl_2
                    pl_2 := pl_1
                    pl_1 := pl_0
                    pl_0 := mPrice
                    plClose_3 := plClose_2
                    plClose_2 := plClose_1
                    plClose_1 := plClose_0
                    plClose_0 := mClose
                    newSwingLow := true
                    lastAcceptedLowBar := mBar
                    // Accept new high
                    ph_4 := ph_3
                    ph_3 := ph_2
                    ph_2 := ph_1
                    ph_1 := ph_0
                    ph_0 := rawPH0
                    phClose_3 := phClose_2
                    phClose_2 := phClose_1
                    phClose_1 := phClose_0
                    phClose_0 := rawPHClose
                    newSwingHigh := true
                    lastAcceptedHighBar := newHighBar
                    missingFound := true
            if not missingFound and not na(ph_0) and rawPH0 > ph_0
                // Fallback: update value if more extreme (no bars between swings)
                ph_0 := rawPH0
                phClose_0 := rawPHClose
                updateSwingHigh := true
                if not na(newHighBar)
                    lastAcceptedHighBar := newHighBar
            lastRawPH := rawPH0
            lastRawPHClose := rawPHClose
    else if rawLowChanged
        if lastWasHigh
            // Accept low (alternates) - shift all values
            pl_4 := pl_3
            pl_3 := pl_2
            pl_2 := pl_1
            pl_1 := pl_0
            pl_0 := rawPL0
            plClose_3 := plClose_2
            plClose_2 := plClose_1
            plClose_1 := plClose_0
            plClose_0 := rawPLClose
            lastWasHigh := false
            newSwingLow := true
            lastRawPL := rawPL0
            lastRawPLClose := rawPLClose
            lastAcceptedLowBar := findSwingLowBar(rawPL0, htfBarsLookback)
        else
            // Consecutive low - find missing high between previous low and this one
            int newLowBar = findSwingLowBar(rawPL0, htfBarsLookback)
            bool missingFound = false
            if not na(newLowBar) and not na(lastAcceptedLowBar)
                [mPrice, mBar, mClose] = findHighestHighBetween(lastAcceptedLowBar, newLowBar)
                if not na(mPrice)
                    // Insert missing high
                    ph_4 := ph_3
                    ph_3 := ph_2
                    ph_2 := ph_1
                    ph_1 := ph_0
                    ph_0 := mPrice
                    phClose_3 := phClose_2
                    phClose_2 := phClose_1
                    phClose_1 := phClose_0
                    phClose_0 := mClose
                    newSwingHigh := true
                    lastAcceptedHighBar := mBar
                    // Accept new low
                    pl_4 := pl_3
                    pl_3 := pl_2
                    pl_2 := pl_1
                    pl_1 := pl_0
                    pl_0 := rawPL0
                    plClose_3 := plClose_2
                    plClose_2 := plClose_1
                    plClose_1 := plClose_0
                    plClose_0 := rawPLClose
                    newSwingLow := true
                    lastAcceptedLowBar := newLowBar
                    missingFound := true
            if not missingFound and not na(pl_0) and rawPL0 < pl_0
                // Fallback: update value if more extreme (no bars between swings)
                pl_0 := rawPL0
                plClose_0 := rawPLClose
                updateSwingLow := true
                if not na(newLowBar)
                    lastAcceptedLowBar := newLowBar
            lastRawPL := rawPL0
            lastRawPLClose := rawPLClose

// ----- Calculate Trend (with Primary/Secondary confirmation) -----
// Uses pivot CLOSE prices to filter out liquidity wicks in the structure analysis.
// CHoCH, liquidity sweep and expansion continue to use high/low prices.
calcSwingsTrend(sh0, sh1, sh2, sl0, sl1, sl2) =>
    // Check if we have enough data
    if na(sh0) or na(sh1) or na(sh2) or na(sl0) or na(sl1) or na(sl2)
        [NODIRECTION, 0]
    else
        // ==============================================
        // TWO-LEVEL LOGIC: Primary + Secondary Confirmation
        // ==============================================

        // PRIMARY ANALYSIS - BULLISH structure based on LOWS
        // Perfect case: LL3 < LL2 < LL1 (index 2 < 1 < 0)
        bool perfectBullish = (sl2 < sl1) and (sl1 < sl0)

        // Sweep case: LL3 > LL2 (sweep) but LL1 > LL3 (recovery above)
        bool sweepBullish = (sl2 > sl1) and (sl0 > sl2)

        // Additional check: LL1 should be higher than LL2 for any bullish scenario
        bool ll1HigherThanLL2 = sl0 > sl1

        bool primaryBullish = (perfectBullish or sweepBullish) and ll1HigherThanLL2

        // Ambiguous case for bullish: LL3 > LL2 < LL1 (V-shape correction)
        bool ambiguousBullish = (sl2 > sl1) and (sl1 < sl0)

        // PRIMARY ANALYSIS - BEARISH structure based on HIGHS
        // Perfect case: HH3 > HH2 > HH1 (index 2 > 1 > 0)
        bool perfectBearish = (sh2 > sh1) and (sh1 > sh0)

        // Sweep case: HH3 < HH2 (sweep) but HH1 < HH3 (breakdown below)
        bool sweepBearish = (sh2 < sh1) and (sh0 < sh2)

        // Additional check: HH1 should be lower than HH2 for any bearish scenario
        bool hh1LowerThanHH2 = sh0 < sh1

        bool primaryBearish = (perfectBearish or sweepBearish) and hh1LowerThanHH2

        // Ambiguous case for bearish: HH3 < HH2 > HH1 (^-shape correction)
        bool ambiguousBearish = (sh2 < sh1) and (sh1 > sh0)

        // SECONDARY CONFIRMATION using opposite swings
        bool highsConfirmBullish = sh0 > sh1  // HH1 > HH2
        bool lowsConfirmBearish = sl0 < sl1   // LL1 < LL2

        // DECISION LOGIC
        if primaryBullish
            // Bullish Momentum: LL1 > LL2 AND HH1 > HH2
            // Bullish Compression: LL1 > LL2 BUT HH1 < HH2
            int status = highsConfirmBullish ? MOMENTUM : COMPRESSION
            [BULLISH, status]
        else if primaryBearish
            // Bearish Momentum: HH1 < HH2 AND LL1 < LL2
            // Bearish Compression: HH1 < HH2 BUT LL1 > LL2
            int status = lowsConfirmBearish ? MOMENTUM : COMPRESSION
            [BEARISH, status]
        else if ambiguousBullish and highsConfirmBullish
            // Ambiguous lows but highs confirm buyer pressure
            [BULLISH, MOMENTUM]
        else if ambiguousBearish and lowsConfirmBearish
            // Ambiguous highs but lows confirm seller pressure
            [BEARISH, MOMENTUM]
        else
            // Structure is unclear
            [NODIRECTION, 0]

// ----- Calculate Liquidity Sweep (3 swings analysis with close price confirmation) -----
// Detects when price takes out liquidity at a swing level before reversing
calcLiquiditySweep(direction, sh0, sh1, sh2, sl0, sl1, sl2, shClose0, slClose0) =>
    // Need 3 swings of each type for proper analysis
    if na(sl0) or na(sl1) or na(sl2) or na(sh0) or na(sh1) or na(sh2)
        false
    else if direction == BULLISH
        // BULLISH: Look at lows for liquidity sweep
        // Case 1: sl1 < sl2 (lower low formed), then sl0 > sl1 AND sh0 > sh1 (price reversed up)
        bool case1 = (sl1 < sl2) and (sl0 > sl1) and (sh0 > sh1)
        // Case 2: sl0 < sl1 (new low made), but close above sl1 OR sh0 > sh1 (rejection or reversal)
        bool case2 = (sl0 < sl1) and ((slClose0 > sl1) or (sh0 > sh1))
        case1 or case2
    else if direction == BEARISH
        // BEARISH: Look at highs for liquidity sweep
        // Case 1: sh1 > sh2 (higher high formed), then sh0 < sh1 AND sl0 < sl1 (price reversed down)
        bool case1 = (sh1 > sh2) and (sh0 < sh1) and (sl0 < sl1)
        // Case 2: sh0 > sh1 (new high made), but close below sh1 OR sl0 < sl1 (rejection or reversal)
        bool case2 = (sh0 > sh1) and ((shClose0 < sh1) or (sl0 < sl1))
        case1 or case2
    else
        false

// ----- Calculate CHoCH (Change of Character) -----
// Dual CHoCH: detects both bullish and bearish CHoCH simultaneously
// When both exist, prioritizes the most recent one (by lastSwingWasHigh)
// If the most recent CHoCH matches prevDir → liquidity sweep pattern
calcCHoCH(sh0, sh1, sh2, sl0, sl1, sl2, sh0Close, sl0Close, prevDir, hasPrevTrend, lastSwingWasHigh) =>
    if na(sh0) or na(sh1) or na(sl0) or na(sl1) or na(sh0Close) or na(sl0Close)
        [CONTINUATION, false]
    else
        // CHoCH BULLISH conditions
        bool hh1AboveHH2 = sh0 > sh1
        bool hh1CandleClosedAboveHH2 = sh0Close > sh1
        bool prevHighsDeclining = not na(sh2) and sh1 < sh2
        bool prevNotBullish = hasPrevTrend ? (prevDir != BULLISH) : prevHighsDeclining
        bool bullishByStructure = prevHighsDeclining and hh1AboveHH2 and hh1CandleClosedAboveHH2
        bool bullishByPrev = prevNotBullish and hh1AboveHH2 and hh1CandleClosedAboveHH2

        // CHoCH BEARISH conditions
        bool ll1BelowLL2 = sl0 < sl1
        bool ll1CandleClosedBelowLL2 = sl0Close < sl1
        bool prevLowsRising = not na(sl2) and sl1 > sl2
        bool prevNotBearish = hasPrevTrend ? (prevDir != BEARISH) : prevLowsRising
        bool bearishByStructure = prevLowsRising and ll1BelowLL2 and ll1CandleClosedBelowLL2
        bool bearishByPrev = prevNotBearish and ll1BelowLL2 and ll1CandleClosedBelowLL2

        // DUAL CHoCH: both structural conditions true → most recent wins
        if bullishByStructure and bearishByStructure
            if lastSwingWasHigh
                // SH0 more recent → CHoCH Bullish wins
                [CHOCH_BULLISH, hasPrevTrend and prevDir == BULLISH]
            else
                // SL0 more recent → CHoCH Bearish wins
                [CHOCH_BEARISH, hasPrevTrend and prevDir == BEARISH]
        else
            // SINGLE CHoCH (using prevDir)
            if bullishByPrev
                [CHOCH_BULLISH, false]
            else if bearishByPrev
                [CHOCH_BEARISH, false]
            else
                [CONTINUATION, false]

// ----- Gate Trend Change (require CHoCH confirmation for reversals) -----
// When the raw trend reverses vs the previous trend, require CHoCH confirmation.
// Without CHoCH, check if structure still supports the previous trend (compression).
// Dual CHoCH liquidity sweep: if both CHoCH detected and most recent matches prevDir,
// the opposing CHoCH was a liquidity sweep → restore previous trend as Momentum.
gateTrendChange(int rawDir, int rawStatus, int prevDir, int choch, bool chochLiqSweep, float sh0, float sh3, float sl0, float sl3) =>
    if chochLiqSweep
        // Dual CHoCH liquidity sweep: restore previous trend as Momentum
        [prevDir, MOMENTUM, true]
    else if prevDir == BULLISH and rawDir == BEARISH
        if choch == CHOCH_BEARISH
            [BEARISH, rawStatus, false]
        else
            if not na(sh0) and not na(sh3) and sh0 > sh3
                [BULLISH, COMPRESSION, false]
            else
                [NODIRECTION, 0, false]
    else if prevDir == BEARISH and rawDir == BULLISH
        if choch == CHOCH_BULLISH
            [BULLISH, rawStatus, false]
        else
            if not na(sl0) and not na(sl3) and sl0 < sl3
                [BEARISH, COMPRESSION, false]
            else
                [NODIRECTION, 0, false]
    else
        [rawDir, rawStatus, false]

// ----- Get current trend, liquidity sweep, and CHoCH (only if valid timeframe) -----
var int swingsDirection = NODIRECTION
var int trendStatus = 0
var int chochStatus = CONTINUATION
var int chochPrevStatus = CONTINUATION
// Calculate previous trend using close prices (swings 1,2,3) - avoids nested function call issues
[prevTrendDir, prevTrendStatus] = calcSwingsTrend(phClose_1, phClose_2, phClose_3, plClose_1, plClose_2, plClose_3)
bool hasPrevTrend = not na(ph_3) and not na(pl_3)

bool gatedLiqSweep = false

if validTimeframe
    [rawDir, rawStatus] = calcSwingsTrend(phClose_0, phClose_1, phClose_2, plClose_0, plClose_1, plClose_2)
    [choch, chochLiqSweep] = calcCHoCH(ph_0, ph_1, ph_2, pl_0, pl_1, pl_2, phClose_0, plClose_0, prevTrendDir, hasPrevTrend, lastWasHigh)
    chochStatus := choch
    [dir, status, gLiqSweep] = gateTrendChange(rawDir, rawStatus, prevTrendDir, chochStatus, chochLiqSweep, ph_0, ph_3, pl_0, pl_3)
    swingsDirection := dir
    trendStatus := status
    gatedLiqSweep := gLiqSweep
else
    swingsDirection := NODIRECTION
    trendStatus := 0
    chochStatus := CONTINUATION

bool liquiditySweep = validTimeframe ? (gatedLiqSweep or calcLiquiditySweep(swingsDirection, ph_0, ph_1, ph_2, pl_0, pl_1, pl_2, phClose_0, plClose_0)) : false

// ----- Draw Swing Icons (only if valid timeframe) -----
if drawSwingIcons and validTimeframe
    // New swing high - create new label (keep previous ones)
    if newSwingHigh and not na(ph_0)
        int swingHighBar = findSwingHighBar(ph_0, htfBarsLookback)
        float gapHigh = ph_0 * (swingIconGapPercent / 100)
        if not na(swingHighBar)
            lastSwingHighLabel := label.new(swingHighBar, ph_0 + gapHigh, "▼",
                 xloc=xloc.bar_index, style=label.style_label_down, textcolor=color.red,
                 color=color.new(color.white, 100), size=size.small)

    // Updated swing high (more extreme) - move the label
    if updateSwingHigh and not na(ph_0)
        int swingHighBar = findSwingHighBar(ph_0, htfBarsLookback)
        float gapHigh = ph_0 * (swingIconGapPercent / 100)
        if not na(swingHighBar)
            if not na(lastSwingHighLabel)
                label.delete(lastSwingHighLabel)
            lastSwingHighLabel := label.new(swingHighBar, ph_0 + gapHigh, "▼",
                 xloc=xloc.bar_index, style=label.style_label_down, textcolor=color.red,
                 color=color.new(color.white, 100), size=size.small)

    // New swing low - create new label (keep previous ones)
    if newSwingLow and not na(pl_0)
        int swingLowBar = findSwingLowBar(pl_0, htfBarsLookback)
        float gapLow = pl_0 * (swingIconGapPercent / 100)
        if not na(swingLowBar)
            lastSwingLowLabel := label.new(swingLowBar, pl_0 - gapLow, "▲",
                 xloc=xloc.bar_index, style=label.style_label_up, textcolor=color.green,
                 color=color.new(color.white, 100), size=size.small)

    // Updated swing low (more extreme) - move the label
    if updateSwingLow and not na(pl_0)
        int swingLowBar = findSwingLowBar(pl_0, htfBarsLookback)
        float gapLow = pl_0 * (swingIconGapPercent / 100)
        if not na(swingLowBar)
            if not na(lastSwingLowLabel)
                label.delete(lastSwingLowLabel)
            lastSwingLowLabel := label.new(swingLowBar, pl_0 - gapLow, "▲",
                 xloc=xloc.bar_index, style=label.style_label_up, textcolor=color.green,
                 color=color.new(color.white, 100), size=size.small)

// ----- Draw CHoCH Lines -----
if drawChochLine and validTimeframe and chochStatus != chochPrevStatus
    if chochStatus == CHOCH_BULLISH and not na(ph_0) and not na(ph_1)
        int barPh1 = findSwingHighBar(ph_1, htfBarsLookback)
        int barPh0 = findSwingHighBar(ph_0, htfBarsLookback)
        if not na(barPh1) and not na(barPh0)
            line.new(barPh1, ph_1, barPh0, ph_1, xloc=xloc.bar_index, color=color.lime, width=1, style=line.style_solid)
    else if chochStatus == CHOCH_BEARISH and not na(pl_0) and not na(pl_1)
        int barPl1 = findSwingLowBar(pl_1, htfBarsLookback)
        int barPl0 = findSwingLowBar(pl_0, htfBarsLookback)
        if not na(barPl1) and not na(barPl0)
            line.new(barPl1, pl_1, barPl0, pl_1, xloc=xloc.bar_index, color=color.red, width=1, style=line.style_solid)
chochPrevStatus := chochStatus

// ----- Dashboard -----
if displayDashboard and barstate.islast
    // Build table (smaller with no separators)
    var table dashboard = table.new(position.top_right, 2, 12, bgcolor=color.new(color.gray, 85), border_width=dashboardBorderWidth)

    if not validTimeframe
        // Show error message when chart TF > swing TF
        table.cell(dashboard, 0, 0, "Chart TF > Swing TF", text_color=color.orange, text_halign=text.align_left, text_size=size.small)
        table.cell(dashboard, 1, 0, "N/A", text_color=color.orange, text_halign=text.align_right, text_size=size.small)
    else
        // Calculate expansion in pips
        float swingsExpansion = na(ph_0) or na(pl_0) ? na : (ph_0 - pl_0) / pipValue

        // Time frame row
        table.cell(dashboard, 0, 0, "TF", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
        table.cell(dashboard, 1, 0, swingTimeFrame, text_color=color.gray, text_halign=text.align_right, text_size=size.small)

        // Direction row (with status: Momentum or Compression)
        string statusText = trendStatus == MOMENTUM ? " (M)" : trendStatus == COMPRESSION ? " (C)" : ""
        string dirText = swingsDirection == BULLISH ? "Bullish" + statusText : swingsDirection == BEARISH ? "Bearish" + statusText : "Unclear"
        color dirColor = swingsDirection == BULLISH ? color.lime : swingsDirection == BEARISH ? color.red : color.gray
        table.cell(dashboard, 0, 1, "Trend", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
        table.cell(dashboard, 1, 1, dirText, text_color=dirColor, text_halign=text.align_right, text_size=size.small)

        // CHoCH row
        string chochText = chochStatus == CHOCH_BULLISH ? "Bullish" : chochStatus == CHOCH_BEARISH ? "Bearish" : "Continuation"
        color chochColor = chochStatus == CHOCH_BULLISH ? color.lime : chochStatus == CHOCH_BEARISH ? color.red : color.gray
        table.cell(dashboard, 0, 2, "CHoCH", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
        table.cell(dashboard, 1, 2, chochText, text_color=chochColor, text_halign=text.align_right, text_size=size.small)

        // Liquidity sweep row
        string liqText = liquiditySweep ? "Yes" : "No"
        table.cell(dashboard, 0, 3, "Liq Sweep", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
        table.cell(dashboard, 1, 3, liqText, text_color=color.gray, text_halign=text.align_right, text_size=size.small)

        // Expansion row
        table.cell(dashboard, 0, 4, "Expansion", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
        table.cell(dashboard, 1, 4, na(swingsExpansion) ? "N/A" : str.tostring(swingsExpansion, "#.##"), text_color=color.aqua, text_halign=text.align_right, text_size=size.small)

        // Swing highs and lows (optional)
        if displaySwingValues
            table.cell(dashboard, 0, 5, "SH -2", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
            table.cell(dashboard, 1, 5, na(ph_2) ? "N/A" : str.tostring(ph_2, format.mintick), text_color=color.gray, text_halign=text.align_right, text_size=size.small)

            table.cell(dashboard, 0, 6, "SH -1", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
            table.cell(dashboard, 1, 6, na(ph_1) ? "N/A" : str.tostring(ph_1, format.mintick), text_color=color.gray, text_halign=text.align_right, text_size=size.small)

            table.cell(dashboard, 0, 7, "SH 0", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
            table.cell(dashboard, 1, 7, na(ph_0) ? "N/A" : str.tostring(ph_0, format.mintick), text_color=color.red, text_halign=text.align_right, text_size=size.small)

            table.cell(dashboard, 0, 8, "SL -2", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
            table.cell(dashboard, 1, 8, na(pl_2) ? "N/A" : str.tostring(pl_2, format.mintick), text_color=color.gray, text_halign=text.align_right, text_size=size.small)

            table.cell(dashboard, 0, 9, "SL -1", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
            table.cell(dashboard, 1, 9, na(pl_1) ? "N/A" : str.tostring(pl_1, format.mintick), text_color=color.gray, text_halign=text.align_right, text_size=size.small)

            table.cell(dashboard, 0, 10, "SL 0", text_color=color.gray, text_halign=text.align_left, text_size=size.small)
            table.cell(dashboard, 1, 10, na(pl_0) ? "N/A" : str.tostring(pl_0, format.mintick), text_color=color.green, text_halign=text.align_right, text_size=size.small)

// ----- Plot hidden values for alerts -----
plot(swingsDirection, "Trend Direction", display=display.none)
plot(chochStatus, "CHoCH Status", display=display.none)
plot(liquiditySweep ? 1 : 0, "Liquidity Sweep", display=display.none)

Overview

JCO Swings Trend HTF calculates trend direction using swing structures with bullish or bearish momentum and compression status. CHoCH detection identifies potential reversals with liquidity sweep confirmation based on pivot close prices. The script draws swing high and low icons on the price chart, optional horizontal CHoCH lines marking broken levels, and a compact dashboard showing trend, CHoCH status, liquidity sweep and expansion. Main settings adjust swing period and timeframe, icon drawing, CHoCH line visibility, dashboard display and pip value scaling.

Details

Published by

jcornierfra on GitHub

Code credit

jcornier, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Trend, SMC / ICT / price action, Screeners & dashboards

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.

Topics

indicatortradingview

Settings

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

SettingTypeDefaultVariable
Swing Periodint5swingPeriod
Swing Time Frametimeframe60swingTimeFrame
Draw IconsbooltruedrawSwingIcons
Draw CHoCH LinebooltruedrawChochLine
Icon Gap (% of price)float0.1swingIconGapPercent
Display DashboardbooltruedisplayDashboard
Show SH/SL ValuesboolfalsedisplaySwingValues
Pip Value ($)float0.1pipValue
Dashboard Border Widthint0dashboardBorderWidth
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

53 stars

Apex Trend & Liquidity Master calculates a trend cloud using customizable moving averages expanded by a volatility multiplier directly on the main price chart. It identifies dynamic support and resistance liquidity zones from swing high and low pivots, extendi

Indicator v6 TrendSupport & resistance
Updated 2 months ago · MPL-2.0 GitHub repository of Apex Trend & Liquidity Master

Confluence Suite

by TamaraKGreen
46 stars

The Confluence Suite indicator draws multiple trading signals and tools directly on the price chart. It generates entry and exit signals based on selected modes and filters, with an AI signal classifier rating quality from 1-4. Additional features include smar

Indicator v5 TrendSMC / ICT / price action
Updated 7 weeks ago · None stated GitHub repository of Confluence Suite

Market State Engine classifies the current market into nine states including balance, compression, bull and bear expansion, pullback, exhaustion, and transition. It computes orthogonal axes for structural regime, energy phase, value location relative to adapti

Indicator v6 TrendSupport & resistance Repaints
Updated yesterday · MIT GitHub repository of Market State Engine [WavesUnchained]

Quantum Structure | jjart

by MeridianAlgo
9 stars

This indicator analyzes market structure and identifies key swing highs, swing lows, and consolidation ranges across multiple timeframes. It automatically adapts swing lengths and consolidation thresholds according to chosen profiles for scalping, intraday, or

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 4 months ago · MIT GitHub repository of Quantum Structure | jjart

This indicator overlays a market structure dashboard on the price chart. It reads up to seven user-selected timeframes, each with an on/off switch and a trend weight, and shows columns for EMA trend, swing highs and lows, structure, order blocks, and fair valu

Indicator v6 Screeners & dashboardsSMC / ICT / price action
Updated 3 months ago · None stated GitHub repository of Market Structure Dashboard | Flux Charts

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