Indicator_JCO_Swings_Trend_HTF.pine
Copy
// 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)
Show all 632 lines