// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
//  _______           _______  _______           _______  _       _________ _        _______ 
// (  ____ \|\     /|(  ____ \(  ____ )|\     /|(  ___  )( \      \__   __/( (    /|(  ___  )
// | (    \/| )   ( || (    \/| (    )|| )   ( || (   ) || (         ) (   |  \  ( || (   ) |
// | |      | (___) || (__    | (____)|| |   | || |   | || |         | |   |   \ | || |   | |
// | |      |  ___  ||  __)   |     __)( (   ) )| |   | || |         | |   | (\ \) || |   | |
// | |      | (   ) || (      | (\ (    \ \_/ / | |   | || |         | |   | | \   || |   | |
// | (____/\| )   ( || (____/\| ) \ \__  \   /  | (___) || (____/\___) (___| )  \  || (___) |
// (_______/|/     \|(_______/|/   \__/   \_/   (_______)(_______/\_______/|/    )_)(_______)
// © chervolino
//@version=6
indicator(title="Trendshift [CHE]", shorttitle="TrShift", overlay=true, max_bars_back=5000, max_lines_count=500, max_labels_count=500, scale=scale.right)

// ——— SECTION: Constants & Enums
string GROUP_MARKET_STRUCTURE = "Market Structure"
string GROUP_FRAMEWORK = "Premium/Discount Framework"
string GROUP_VISUAL = "Visuals"

// ——— SECTION: Inputs (grouped; tooltips; one-row via inline)
swing_len_input = input.int(5, "Swing length", minval=1, group=GROUP_MARKET_STRUCTURE, inline="msw", tooltip="Bars left/right for major swing highs and lows.")
is_use_atr_filter_input = input.bool(true, "Use ATR filter", group=GROUP_MARKET_STRUCTURE, inline="msw", tooltip="Require breakout to exceed swing level by an ATR multiple.")
atr_len_input = input.int(14, "ATR length", minval=1, group=GROUP_MARKET_STRUCTURE, inline="atr", tooltip="ATR length for conviction and band quality checks.")
atr_mult_break_input = input.float(1.0, "Break ATR mult", minval=0.0, step=0.1, group=GROUP_MARKET_STRUCTURE, inline="atr", tooltip="How far beyond the swing level price must move, in ATR units, to confirm a structure shift.")

is_enable_framework_input = input.bool(true, "Enable framework", group=GROUP_FRAMEWORK, inline="fw", tooltip="If disabled, only structure shifts are computed and no band is used.")
is_persist_last_band_input = input.bool(true, "Persist band on timeout", group=GROUP_FRAMEWORK, inline="fw", tooltip="If true, keep last band after regime timeout; otherwise clear it.")
min_band_atr_mult_input = input.float(0.5, "Min band size ATR mult", minval=0.0, step=0.1, group=GROUP_FRAMEWORK, inline="band", tooltip="Minimum band height relative to ATR required for a valid premium/discount band.")
regime_timeout_bars_input = input.int(500, "Regime timeout bars", minval=0, group=GROUP_FRAMEWORK, inline="band", tooltip="Maximum bars since last structure shift before regime is neutralized. Set 0 to disable timeout.")
is_invert_colors_input = input.bool(true, "Invert colors", group=GROUP_FRAMEWORK, inline="col", tooltip="Swap visual colors for premium and discount zones.")

show_zone_tint_input = input.bool(true, "Show zone tint", group=GROUP_VISUAL, inline="vis", tooltip="Tint background when price is in discount or premium.")
show_structure_marks_input = input.bool(true, "Show shift markers", group=GROUP_VISUAL, inline="vis", tooltip="Show only the first bullish and first bearish structure shift markers per direction.")

// ——— SECTION: Types
// (no custom types)

// ——— SECTION: Persistent Vars → Runtime Vars
var float last_swing_high = na
var float last_swing_low = na
var int last_swing_high_bar = na
var int last_swing_low_bar = na

var int regime = 0
var float band_low = na
var float band_high = na
var float struct_level = na

var int last_shift_bar = na
var bool is_last_shift_bullish = false

// ——— SECTION: Helpers (pure, no side effects)
// (no helpers required)

// ——— SECTION: Core Calculations

// Swings and ATR
pivot_high_value = ta.pivothigh(high, swing_len_input, swing_len_input)
pivot_low_value = ta.pivotlow(low, swing_len_input, swing_len_input)

if not na(pivot_high_value)
    last_swing_high := pivot_high_value
    last_swing_high_bar := bar_index - swing_len_input

if not na(pivot_low_value)
    last_swing_low := pivot_low_value
    last_swing_low_bar := bar_index - swing_len_input

atr_value = ta.atr(atr_len_input)

// Structure shifts
has_major_high = not na(last_swing_high)
has_major_low = not na(last_swing_low)

is_bullish_break_base = has_major_high and close > last_swing_high
is_bearish_break_base = has_major_low and close < last_swing_low

is_bullish_break = is_bullish_break_base
is_bearish_break = is_bearish_break_base

if is_use_atr_filter_input and atr_value > 0.0
    is_bullish_break := is_bullish_break_base and (close - last_swing_high) >= atr_mult_break_input * atr_value
    is_bearish_break := is_bearish_break_base and (last_swing_low - close) >= atr_mult_break_input * atr_value

is_bullish_shift = is_bullish_break and has_major_low
is_bearish_shift = is_bearish_break and has_major_high

if is_bullish_shift and is_bearish_shift
    is_bullish_shift := close >= open
    is_bearish_shift := not is_bullish_shift

is_bullish_shift_first = is_bullish_shift and (na(last_shift_bar) or not is_last_shift_bullish)
is_bearish_shift_first = is_bearish_shift and (na(last_shift_bar) or is_last_shift_bullish)

// Regime and band update (all confirmed shifts)
if is_bullish_shift
    regime := 1
    band_low := last_swing_low
    band_high := high
    struct_level := last_swing_high
    last_shift_bar := bar_index
    is_last_shift_bullish := true

if is_bearish_shift
    regime := -1
    band_low := low
    band_high := last_swing_high
    struct_level := last_swing_low
    last_shift_bar := bar_index
    is_last_shift_bullish := false

// Regime timeout and optional band clear
if regime_timeout_bars_input > 0 and regime != 0 and not is_bullish_shift and not is_bearish_shift and not na(last_shift_bar) and bar_index - last_shift_bar > regime_timeout_bars_input
    regime := 0
    if not is_persist_last_band_input
        band_low := na
        band_high := na
        struct_level := na

// Premium/discount band
is_base_valid_band = is_enable_framework_input and not na(band_low) and not na(band_high) and band_high > band_low
price_span = is_base_valid_band ? band_high - band_low : na
is_band_not_tiny = is_base_valid_band and atr_value > 0.0 and min_band_atr_mult_input > 0.0 and price_span >= min_band_atr_mult_input * atr_value or is_base_valid_band and (min_band_atr_mult_input == 0.0 or atr_value <= 0.0)
is_valid_band = is_base_valid_band and is_band_not_tiny

discount_threshold = is_valid_band ? band_low + 0.25 * price_span : na
premium_threshold = is_valid_band ? band_low + 0.75 * price_span : na

is_in_discount = is_valid_band and close <= discount_threshold
is_in_premium = is_valid_band and close >= premium_threshold

// ——— SECTION: Rendering/UI (GLOBAL ONLY; single-line calls)
discount_zone_color = is_invert_colors_input ? color.new(color.red, 30) : color.new(color.green, 30)
premium_zone_color = is_invert_colors_input ? color.new(color.green, 30) : color.new(color.red, 30)

discount_bg_color = show_zone_tint_input and is_in_discount ? color.new(discount_zone_color, 85) : na
premium_bg_color = show_zone_tint_input and is_in_premium ? color.new(premium_zone_color, 85) : na

bgcolor(discount_bg_color, title="Discount zone tint")
bgcolor(premium_bg_color, title="Premium zone tint")

plotshape(show_structure_marks_input and is_bullish_shift_first ? low : na, title="Bullish structure shift (first)", style=shape.triangleup, location=location.belowbar, size=size.small, color=color.new(color.lime, 0), text="Shift↑", textcolor=color.white)
plotshape(show_structure_marks_input and is_bearish_shift_first ? high : na, title="Bearish structure shift (first)", style=shape.triangledown, location=location.abovebar, size=size.small, color=color.new(color.red, 0), text="Shift↓", textcolor=color.white)
