wick_rejection_sr_strategy_v2.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/
// © KingdomFinancier - Wick Rejection S/R Trading Strategy
// Version: 2.0 (Optimized & Bug-Fixed)
// Based on: Liquidity Sweep & Wick Rejection Scalping Methodology
//@version=5
indicator("Wick Rejection S/R Strategy v2", shorttitle="WR-SR v2", overlay=true, max_lines_count=100, max_boxes_count=100, max_labels_count=100)
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS - LEVEL DETECTION
// ═══════════════════════════════════════════════════════════════════════════════
grp_levels = "══════ Level Detection ══════"
lookback_period = input.int(50, "Lookback Period", minval=20, maxval=200, group=grp_levels, tooltip="Number of candles to scan for rejection wicks")
wick_threshold = input.float(0.50, "Wick Threshold %", minval=0.30, maxval=0.80, step=0.05, group=grp_levels, tooltip="Minimum wick size as % of candle range")
level_proximity = input.float(2.0, "Level Merge Distance", minval=0.5, maxval=20.0, step=0.5, group=grp_levels, tooltip="Points tolerance for merging nearby levels")
max_levels = input.int(6, "Max Levels to Show", minval=2, maxval=12, group=grp_levels)
zone_buffer = input.float(1.5, "Zone Buffer (points)", minval=0.5, maxval=10.0, step=0.5, group=grp_levels, tooltip="Buffer above/below level for zone definition")
min_candle_range = input.float(1.0, "Min Candle Range", minval=0.1, maxval=10.0, step=0.1, group=grp_levels, tooltip="Ignore tiny candles below this range")
level_expiration = input.int(200, "Level Expiration (bars)", minval=50, maxval=500, group=grp_levels, tooltip="Levels older than this will be removed")
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS - TREND FILTER
// ═══════════════════════════════════════════════════════════════════════════════
grp_trend = "══════ Trend Filter ══════"
ema_length = input.int(9, "EMA Length", minval=5, maxval=50, group=grp_trend)
use_ema_filter = input.bool(true, "Use EMA Filter", group=grp_trend, tooltip="Only allow signals aligned with EMA direction")
require_h1_align = input.bool(true, "Require H1 Confluence", group=grp_trend, tooltip="Signal must align with H1 trend")
allow_reversal_trades = input.bool(true, "Allow Reversal Trades", group=grp_trend, tooltip="Allow high-confidence counter-trend trades at strong S/R levels")
reversal_min_confidence = input.int(4, "Reversal Min Confidence", minval=3, maxval=5, group=grp_trend, tooltip="Minimum confidence for counter-trend trades (3-5)")
show_ema = input.bool(true, "Show EMA Line", group=grp_trend)
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS - CISD (Change in State of Delivery)
// ═══════════════════════════════════════════════════════════════════════════════
grp_cisd = "══════ CISD Settings ══════"
use_cisd = input.bool(true, "Enable CISD", group=grp_cisd, tooltip="Track momentum shift for early reversal detection")
cisd_min_duration = input.int(3, "Min CISD Duration", minval=0, maxval=20, group=grp_cisd, tooltip="Minimum bars before CISD signal (0-20)")
cisd_max_duration = input.int(50, "Max CISD Validity", minval=10, maxval=200, group=grp_cisd, tooltip="Maximum bars to track CISD state (10-200)")
cisd_confidence_boost = input.int(1, "CISD Confidence Boost", minval=1, maxval=2, group=grp_cisd, tooltip="Bonus confidence when CISD detected (1-2)")
show_cisd_labels = input.bool(true, "Show CISD Labels", group=grp_cisd, tooltip="Display CISD markers on chart")
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS - SIGNAL SETTINGS
// ═══════════════════════════════════════════════════════════════════════════════
grp_signals = "══════ Signal Settings ══════"
show_zone_test = input.bool(true, "Show Zone Test Signals", group=grp_signals)
show_sweeps = input.bool(true, "Show Liquidity Sweeps", group=grp_signals)
show_breakout_retest = input.bool(true, "Show Breakout Retests", group=grp_signals)
min_confidence = input.int(2, "Minimum Confidence Score", minval=1, maxval=5, group=grp_signals, tooltip="Minimum score (1-5) to show signal")
signal_cooldown = input.int(8, "Signal Cooldown (bars)", minval=1, maxval=20, group=grp_signals, tooltip="Minimum bars between signals at same level")
max_signal_labels = input.int(5, "Max Signal Labels", minval=3, maxval=10, group=grp_signals, tooltip="Maximum number of signal labels to show on chart")
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS - VISUALS
// ═══════════════════════════════════════════════════════════════════════════════
grp_visuals = "══════ Visual Settings ══════"
resistance_color = input.color(color.red, "Resistance Color", group=grp_visuals)
support_color = input.color(color.red, "Support Color", group=grp_visuals)
pivot_color = input.color(color.orange, "Pivot Level Color", group=grp_visuals)
zone_transparency = input.int(90, "Zone Transparency", minval=70, maxval=98, group=grp_visuals)
show_zones = input.bool(true, "Show Zone Boxes", group=grp_visuals)
show_labels = input.bool(true, "Show Price Labels", group=grp_visuals)
show_info_table = input.bool(true, "Show Info Table", group=grp_visuals)
table_text_size = input.string("Normal", "Table Font Size", options=["Small", "Normal", "Large"], group=grp_visuals, tooltip="Adjust info table text size for better readability")
buy_signal_color = input.color(color.green, "Buy Signal Color", group=grp_visuals)
sell_signal_color = input.color(color.red, "Sell Signal Color", group=grp_visuals)
sweep_color = input.color(color.blue, "Liquidity Sweep Color", group=grp_visuals)
// ═══════════════════════════════════════════════════════════════════════════════
// INPUTS - ALERTS
// ═══════════════════════════════════════════════════════════════════════════════
grp_alerts = "══════ Alert Settings ══════"
alert_zone_test = input.bool(true, "Alert on Zone Test", group=grp_alerts)
alert_signal = input.bool(true, "Alert on Entry Signal", group=grp_alerts)
alert_sweep = input.bool(true, "Alert on Liquidity Sweep", group=grp_alerts)
alert_breakout = input.bool(true, "Alert on Breakout Retest", group=grp_alerts)
// ═══════════════════════════════════════════════════════════════════════════════
// CALCULATIONS - EMA & HTF DATA
// ═══════════════════════════════════════════════════════════════════════════════
ema_9 = ta.ema(close, ema_length)
// H1 Data for confluence
[h1_close, h1_ema9] = request.security(syminfo.tickerid, "60", [close, ta.ema(close, ema_length)], lookahead=barmerge.lookahead_off)
// Trend determination
m5_bullish = close > ema_9
m5_bearish = close < ema_9
h1_bullish = h1_close > h1_ema9
h1_bearish = h1_close < h1_ema9
// Combined bias with clear logic
f_has_h1_bullish_confluence() => not require_h1_align or h1_bullish
f_has_h1_bearish_confluence() => not require_h1_align or h1_bearish
overall_bullish = m5_bullish and f_has_h1_bullish_confluence()
overall_bearish = m5_bearish and f_has_h1_bearish_confluence()
// ═══════════════════════════════════════════════════════════════════════════════
// CISD (Change in State of Delivery) TRACKING
// ═══════════════════════════════════════════════════════════════════════════════
// Track candle direction changes and momentum shifts
bull_candle = close > open
bear_candle = close < open
// CISD State Variables
var float cisd_bull_level = na // Tracking level for bullish CISD
var int cisd_bull_start = 0 // Bar index when bull tracking started
var bool cisd_bull_active = false // Is bullish CISD tracking active
var bool cisd_bearish_signal = false // Bearish CISD detected this bar
var float cisd_bear_level = na // Tracking level for bearish CISD
var int cisd_bear_start = 0 // Bar index when bear tracking started
var bool cisd_bear_active = false // Is bearish CISD tracking active
var bool cisd_bullish_signal = false // Bullish CISD detected this bar
// Reset CISD signals each bar
cisd_bearish_signal := false
cisd_bullish_signal := false
if use_cisd and barstate.isconfirmed
// === BEARISH CISD DETECTION ===
// Start tracking when candles change from bearish to bullish
if bull_candle and bear_candle[1]
// Reset previous tracking
if cisd_bull_active and not na(cisd_bull_level)
cisd_bull_active := false
// Start new tracking from current open
cisd_bull_level := open
cisd_bull_start := bar_index
cisd_bull_active := true
// Check if we break back below the tracked level (BEARISH CISD)
if cisd_bull_active and not na(cisd_bull_level)
// Check if tracking is still valid (not expired)
if bar_index - cisd_bull_start <= cisd_max_duration
// Check if price broke back below tracked level
if close < cisd_bull_level
// Minimum duration met?
if bar_index - cisd_bull_start >= cisd_min_duration
cisd_bearish_signal := true
if show_cisd_labels
label.new(bar_index, high, "CISD ▼", style=label.style_label_down,
color=color.new(sell_signal_color, 80), textcolor=sell_signal_color, size=size.tiny)
cisd_bull_active := false
else
// Expired, reset
cisd_bull_active := false
// === BULLISH CISD DETECTION ===
// Start tracking when candles change from bullish to bearish
if bear_candle and bull_candle[1]
// Reset previous tracking
if cisd_bear_active and not na(cisd_bear_level)
cisd_bear_active := false
// Start new tracking from current open
cisd_bear_level := open
cisd_bear_start := bar_index
cisd_bear_active := true
// Check if we break back above the tracked level (BULLISH CISD)
if cisd_bear_active and not na(cisd_bear_level)
// Check if tracking is still valid (not expired)
if bar_index - cisd_bear_start <= cisd_max_duration
// Check if price broke back above tracked level
if close > cisd_bear_level
// Minimum duration met?
if bar_index - cisd_bear_start >= cisd_min_duration
cisd_bullish_signal := true
if show_cisd_labels
label.new(bar_index, low, "CISD ▲", style=label.style_label_up,
color=color.new(buy_signal_color, 80), textcolor=buy_signal_color, size=size.tiny)
cisd_bear_active := false
else
// Expired, reset
cisd_bear_active := false
// ═══════════════════════════════════════════════════════════════════════════════
// FUNCTIONS - WICK CALCULATIONS
// ═══════════════════════════════════════════════════════════════════════════════
f_candle_range() => high - low
f_body_top() => math.max(open, close)
f_body_bottom() => math.min(open, close)
f_upper_wick() => high - f_body_top()
f_lower_wick() => f_body_bottom() - low
f_body_size() => math.abs(close - open)
f_upper_wick_ratio() =>
range_size = f_candle_range()
range_size > min_candle_range ? f_upper_wick() / range_size : 0.0
f_lower_wick_ratio() =>
range_size = f_candle_range()
range_size > min_candle_range ? f_lower_wick() / range_size : 0.0
f_is_bullish_rejection() => f_lower_wick_ratio() >= wick_threshold
f_is_bearish_rejection() => f_upper_wick_ratio() >= wick_threshold
// ═══════════════════════════════════════════════════════════════════════════════
// LEVEL STORAGE - Using Type for Clean Data Management
// ═══════════════════════════════════════════════════════════════════════════════
type LevelData
float price
int touches
int bar_created
int last_signal_bar
line level_line
box level_box
label level_label
var LevelData[] support_data = array.new<LevelData>()
var LevelData[] resistance_data = array.new<LevelData>()
// Track levels changed flag for optimized redraw
var bool levels_changed = false
// ═══════════════════════════════════════════════════════════════════════════════
// FUNCTIONS - LEVEL MANAGEMENT (FIXED: No Level Drift)
// ═══════════════════════════════════════════════════════════════════════════════
f_find_nearby_level_idx(LevelData[] levels, float price, float proximity) =>
idx = -1
if array.size(levels) > 0
for i = 0 to array.size(levels) - 1
level_obj = array.get(levels, i)
if math.abs(level_obj.price - price) <= proximity
idx := i
break
idx
// Delete drawings for a level (FIXED: Synchronized cleanup)
f_delete_level_drawings(LevelData level_obj) =>
if not na(level_obj.level_line)
line.delete(level_obj.level_line)
if not na(level_obj.level_box)
box.delete(level_obj.level_box)
if not na(level_obj.level_label)
label.delete(level_obj.level_label)
// Remove oldest/weakest level with proper cleanup
f_remove_weakest_level(LevelData[] levels) =>
if array.size(levels) > 0
min_score = 999999.0
min_idx = 0
for i = 0 to array.size(levels) - 1
level_obj = array.get(levels, i)
// Score: touches weighted against age
age = bar_index - level_obj.bar_created
score = level_obj.touches * 10.0 - age * 0.1
if score < min_score
min_score := score
min_idx := i
// Delete drawings before removing
level_to_remove = array.get(levels, min_idx)
f_delete_level_drawings(level_to_remove)
array.remove(levels, min_idx)
// Add or update support level (FIXED: No price drift)
f_add_or_update_support(float price) =>
changed = false
nearby_idx = f_find_nearby_level_idx(support_data, price, level_proximity)
if nearby_idx >= 0
// FIXED: Only increment touches, DON'T change price
level_obj = array.get(support_data, nearby_idx)
level_obj.touches += 1
level_obj.bar_created := bar_index // Refresh age
array.set(support_data, nearby_idx, level_obj)
changed := true
else
// Add new level
if array.size(support_data) >= max_levels
f_remove_weakest_level(support_data)
new_level = LevelData.new()
new_level.price := price
new_level.touches := 1
new_level.bar_created := bar_index
new_level.last_signal_bar := 0
new_level.level_line := na
new_level.level_box := na
new_level.level_label := na
array.push(support_data, new_level)
changed := true
changed
// Add or update resistance level (FIXED: No price drift)
f_add_or_update_resistance(float price) =>
changed = false
nearby_idx = f_find_nearby_level_idx(resistance_data, price, level_proximity)
if nearby_idx >= 0
level_obj = array.get(resistance_data, nearby_idx)
level_obj.touches += 1
level_obj.bar_created := bar_index
array.set(resistance_data, nearby_idx, level_obj)
changed := true
else
if array.size(resistance_data) >= max_levels
f_remove_weakest_level(resistance_data)
new_level = LevelData.new()
new_level.price := price
new_level.touches := 1
new_level.bar_created := bar_index
new_level.last_signal_bar := 0
new_level.level_line := na
new_level.level_box := na
new_level.level_label := na
array.push(resistance_data, new_level)
changed := true
changed
// ═══════════════════════════════════════════════════════════════════════════════
// LEVEL EXPIRATION - Remove Stale Levels
// ═══════════════════════════════════════════════════════════════════════════════
f_expire_old_levels() =>
changed = false
// Expire support levels
if array.size(support_data) > 0
for i = array.size(support_data) - 1 to 0
level_obj = array.get(support_data, i)
if bar_index - level_obj.bar_created > level_expiration
f_delete_level_drawings(level_obj)
array.remove(support_data, i)
changed := true
// Expire resistance levels
if array.size(resistance_data) > 0
for i = array.size(resistance_data) - 1 to 0
level_obj = array.get(resistance_data, i)
if bar_index - level_obj.bar_created > level_expiration
f_delete_level_drawings(level_obj)
array.remove(resistance_data, i)
changed := true
changed
// Run expiration check
levels_changed := f_expire_old_levels() or levels_changed
// ═══════════════════════════════════════════════════════════════════════════════
// LEVEL DETECTION - Scan for Rejection Wicks
// ═══════════════════════════════════════════════════════════════════════════════
if barstate.isconfirmed
// Bullish rejection (support level) - long lower wick
if f_is_bullish_rejection()
levels_changed := f_add_or_update_support(low) or levels_changed
// Bearish rejection (resistance level) - long upper wick
if f_is_bearish_rejection()
levels_changed := f_add_or_update_resistance(high) or levels_changed
// ═══════════════════════════════════════════════════════════════════════════════
// ASYMMETRIC ZONE BUFFERS
// ═══════════════════════════════════════════════════════════════════════════════
// For support: smaller buffer below (tighter stop), larger above (more room to enter)
// For resistance: smaller buffer above (tighter stop), larger below (more room to enter)
support_zone_above = zone_buffer * 1.2 // More room above support for entry
support_zone_below = zone_buffer * 0.8 // Tighter below for stop placement
resist_zone_above = zone_buffer * 0.8 // Tighter above for stop placement
resist_zone_below = zone_buffer * 1.2 // More room below resistance for entry
// ═══════════════════════════════════════════════════════════════════════════════
// SIGNAL DETECTION (FIXED: Priority Logic - Sweeps > Zone Tests)
// ═══════════════════════════════════════════════════════════════════════════════
var int last_buy_bar = 0
var int last_sell_bar = 0
// Signal output variables
var bool buy_signal = false
var bool sell_signal = false
var bool is_sweep_signal = false
var bool is_breakout_retest = false
var float signal_level = 0.0
var int signal_confidence = 0
var string signal_type = ""
var float suggested_sl = 0.0
// Reset signals each bar
buy_signal := false
sell_signal := false
is_sweep_signal := false
is_breakout_retest := false
signal_confidence := 0
signal_type := ""
// ═══════════════════════════════════════════════════════════════════════════════
// CHECK SUPPORT SIGNALS (FIXED: Priority Logic)
// ═══════════════════════════════════════════════════════════════════════════════
f_check_support_signals() =>
_signal_found = false
_is_sweep = false
_level = 0.0
_conf = 0
_type = ""
_sl = 0.0
if array.size(support_data) > 0 and barstate.isconfirmed
for i = 0 to array.size(support_data) - 1
if _signal_found
break
level_obj = array.get(support_data, i)
level_price = level_obj.price
touches = level_obj.touches
last_sig = level_obj.last_signal_bar
// Check cooldown
if bar_index - last_sig < signal_cooldown and last_sig > 0
continue
// Asymmetric zone bounds
zone_top = level_price + support_zone_above
zone_bottom = level_price - support_zone_below
// Detection conditions
in_zone = low <= zone_top and low >= zone_bottom
swept = low < level_price and close > level_price // Broke below, closed above
rejecting = in_zone and f_is_bullish_rejection()
// PRIORITY 1: Liquidity Sweep (highest priority)
if show_sweeps and swept
_is_sweep := true
_signal_found := true
_level := level_price
_sl := low - (zone_buffer * 0.5) // SL below the sweep wick
// Confidence calculation (trend-agnostic for reversal mode)
_conf := 3 // Base for sweep
_conf += touches >= 3 ? 1 : 0 // Strong level
// For reversal mode: always give trend bonus (it will be filtered later if needed)
_conf += 1 // Trend alignment bonus (always add for reversal capability)
_conf += 1 // HTF alignment bonus (always add for reversal capability)
// CISD boost
if use_cisd and cisd_bullish_signal
_conf += cisd_confidence_boost
_type := "LIQUIDITY SWEEP"
// Update last signal bar
level_obj.last_signal_bar := bar_index
array.set(support_data, i, level_obj)
// PRIORITY 2: Zone Test (only if no sweep found)
else if show_zone_test and rejecting and not _signal_found
// Confidence calculation (trend-agnostic for reversal mode)
conf = 1 // Base
conf += touches >= 3 ? 1 : 0 // Strong level
conf += f_lower_wick_ratio() > 0.6 ? 1 : 0 // Strong rejection
// For reversal mode: always give trend bonus (it will be filtered later if needed)
conf += 1 // Trend alignment potential
conf += 1 // HTF alignment potential
// CISD boost
if use_cisd and cisd_bullish_signal
conf += cisd_confidence_boost
if conf >= min_confidence
_signal_found := true
_level := level_price
_sl := zone_bottom - (zone_buffer * 0.5)
_conf := conf
_type := "ZONE TEST"
level_obj.last_signal_bar := bar_index
array.set(support_data, i, level_obj)
[_signal_found, _is_sweep, _level, _conf, _type, _sl]
// ═══════════════════════════════════════════════════════════════════════════════
// CHECK RESISTANCE SIGNALS (FIXED: Priority Logic)
// ═══════════════════════════════════════════════════════════════════════════════
f_check_resistance_signals() =>
_signal_found = false
_is_sweep = false
_level = 0.0
_conf = 0
_type = ""
_sl = 0.0
if array.size(resistance_data) > 0 and barstate.isconfirmed
for i = 0 to array.size(resistance_data) - 1
if _signal_found
break
level_obj = array.get(resistance_data, i)
level_price = level_obj.price
touches = level_obj.touches
last_sig = level_obj.last_signal_bar
if bar_index - last_sig < signal_cooldown and last_sig > 0
continue
// Asymmetric zone bounds
zone_top = level_price + resist_zone_above
zone_bottom = level_price - resist_zone_below
in_zone = high >= zone_bottom and high <= zone_top
swept = high > level_price and close < level_price // Broke above, closed below
rejecting = in_zone and f_is_bearish_rejection()
// PRIORITY 1: Liquidity Sweep
if show_sweeps and swept
_is_sweep := true
_signal_found := true
_level := level_price
_sl := high + (zone_buffer * 0.5)
// Confidence calculation (trend-agnostic for reversal mode)
_conf := 3 // Base for sweep
_conf += touches >= 3 ? 1 : 0 // Strong level
// For reversal mode: always give trend bonus (it will be filtered later if needed)
_conf += 1 // Trend alignment bonus (always add for reversal capability)
_conf += 1 // HTF alignment bonus (always add for reversal capability)
// CISD boost
if use_cisd and cisd_bearish_signal
_conf += cisd_confidence_boost
_type := "LIQUIDITY SWEEP"
level_obj.last_signal_bar := bar_index
array.set(resistance_data, i, level_obj)
// PRIORITY 2: Zone Test
else if show_zone_test and rejecting and not _signal_found
// Confidence calculation (trend-agnostic for reversal mode)
conf = 1 // Base
conf += touches >= 3 ? 1 : 0 // Strong level
conf += f_upper_wick_ratio() > 0.6 ? 1 : 0 // Strong rejection
// For reversal mode: always give trend bonus (it will be filtered later if needed)
conf += 1 // Trend alignment potential
conf += 1 // HTF alignment potential
// CISD boost
if use_cisd and cisd_bearish_signal
conf += cisd_confidence_boost
if conf >= min_confidence
_signal_found := true
_level := level_price
_sl := zone_top + (zone_buffer * 0.5)
_conf := conf
_type := "ZONE TEST"
level_obj.last_signal_bar := bar_index
array.set(resistance_data, i, level_obj)
[_signal_found, _is_sweep, _level, _conf, _type, _sl]
// Execute signal checks
[buy_found, buy_is_sweep, buy_level, buy_conf, buy_type, buy_sl] = f_check_support_signals()
[sell_found, sell_is_sweep, sell_level, sell_conf, sell_type, sell_sl] = f_check_resistance_signals()
// Apply EMA filter and set final signals (with reversal mode)
// BUY signals: normal trend filter OR high-confidence reversal
if buy_found and (not use_ema_filter or overall_bullish or (allow_reversal_trades and buy_conf >= reversal_min_confidence))
buy_signal := true
is_sweep_signal := buy_is_sweep
signal_level := buy_level
signal_confidence := buy_conf
signal_type := buy_type
suggested_sl := buy_sl
last_buy_bar := bar_index
// SELL signals: normal trend filter OR high-confidence reversal
if sell_found and (not use_ema_filter or overall_bearish or (allow_reversal_trades and sell_conf >= reversal_min_confidence))
sell_signal := true
is_sweep_signal := sell_is_sweep
signal_level := sell_level
signal_confidence := sell_conf
signal_type := sell_type
suggested_sl := sell_sl
last_sell_bar := bar_index
// ═══════════════════════════════════════════════════════════════════════════════
// BREAKOUT-RETEST DETECTION
// ═══════════════════════════════════════════════════════════════════════════════
type BrokenLevel
float price
int bar_broken
bool was_support // true = was support (now resistance), false = was resistance (now support)
var BrokenLevel[] broken_levels = array.new<BrokenLevel>()
// Track breakouts
if barstate.isconfirmed
// Check support breaks
if array.size(support_data) > 0
for i = array.size(support_data) - 1 to 0
level_obj = array.get(support_data, i)
// Confirmed break: closed below support zone
if close < level_obj.price - support_zone_below
// Add to broken levels
broken = BrokenLevel.new()
broken.price := level_obj.price
broken.bar_broken := bar_index
broken.was_support := true
array.push(broken_levels, broken)
// Limit broken levels array
if array.size(broken_levels) > 10
array.shift(broken_levels)
// Check resistance breaks
if array.size(resistance_data) > 0
for i = array.size(resistance_data) - 1 to 0
level_obj = array.get(resistance_data, i)
if close > level_obj.price + resist_zone_above
broken = BrokenLevel.new()
broken.price := level_obj.price
broken.bar_broken := bar_index
broken.was_support := false
array.push(broken_levels, broken)
if array.size(broken_levels) > 10
array.shift(broken_levels)
// Clean old broken levels
if array.size(broken_levels) > 0
for i = array.size(broken_levels) - 1 to 0
bl = array.get(broken_levels, i)
if bar_index - bl.bar_broken > 100 // Expire after 100 bars
array.remove(broken_levels, i)
// Check for breakout retests (only if no other signal found)
if show_breakout_retest and barstate.isconfirmed and not buy_signal and not sell_signal
if array.size(broken_levels) > 0
for i = 0 to array.size(broken_levels) - 1
bl = array.get(broken_levels, i)
bars_since_break = bar_index - bl.bar_broken
// Must be recent break (3-50 bars ago)
if bars_since_break >= 3 and bars_since_break <= 50
// Broken resistance (now support) - look for buy
if not bl.was_support
if low <= bl.price + zone_buffer and low >= bl.price - zone_buffer
if f_is_bullish_rejection() and (not use_ema_filter or overall_bullish)
buy_signal := true
is_breakout_retest := true
signal_level := bl.price
signal_confidence := 4
signal_type := "BREAKOUT RETEST"
suggested_sl := low - zone_buffer
break
// Broken support (now resistance) - look for sell
if bl.was_support
if high >= bl.price - zone_buffer and high <= bl.price + zone_buffer
if f_is_bearish_rejection() and (not use_ema_filter or overall_bearish)
sell_signal := true
is_breakout_retest := true
signal_level := bl.price
signal_confidence := 4
signal_type := "BREAKOUT RETEST"
suggested_sl := high + zone_buffer
break
// ═══════════════════════════════════════════════════════════════════════════════
// VISUALIZATION (OPTIMIZED: Only redraw when needed)
// ═══════════════════════════════════════════════════════════════════════════════
f_draw_support_levels() =>
if array.size(support_data) > 0
for i = 0 to array.size(support_data) - 1
level_obj = array.get(support_data, i)
level_price = level_obj.price
touches = level_obj.touches
// Line styling based on strength
lwidth = touches >= 4 ? 3 : touches >= 2 ? 2 : 1
lstyle = touches >= 3 ? line.style_solid : line.style_dashed
// Calculate age-based alpha (older = more faded)
age = bar_index - level_obj.bar_created
age_factor = math.max(0.3, 1.0 - (age / level_expiration) * 0.5)
line_alpha = math.round((1.0 - age_factor) * 50)
// Delete old drawings
f_delete_level_drawings(level_obj)
// Create new line
level_obj.level_line := line.new(
bar_index - lookback_period, level_price,
bar_index + 20, level_price,
color=color.new(support_color, line_alpha),
width=lwidth, style=lstyle, extend=extend.right)
// Create zone box with asymmetric buffers
if show_zones
level_obj.level_box := box.new(
bar_index - lookback_period, level_price + support_zone_above,
bar_index + 20, level_price - support_zone_below,
bgcolor=color.new(color.green, zone_transparency),
border_color=color.new(color.green, 80), extend=extend.right)
// Price label
if show_labels
touch_text = touches > 1 ? " (" + str.tostring(touches) + "x)" : ""
level_obj.level_label := label.new(
bar_index + 25, level_price,
str.tostring(level_price, format.mintick) + touch_text,
style=label.style_label_left,
color=color.new(support_color, 60),
textcolor=color.white, size=size.small)
array.set(support_data, i, level_obj)
f_draw_resistance_levels() =>
if array.size(resistance_data) > 0
for i = 0 to array.size(resistance_data) - 1
level_obj = array.get(resistance_data, i)
level_price = level_obj.price
touches = level_obj.touches
lwidth = touches >= 4 ? 3 : touches >= 2 ? 2 : 1
lstyle = touches >= 3 ? line.style_solid : line.style_dashed
age = bar_index - level_obj.bar_created
age_factor = math.max(0.3, 1.0 - (age / level_expiration) * 0.5)
line_alpha = math.round((1.0 - age_factor) * 50)
f_delete_level_drawings(level_obj)
level_obj.level_line := line.new(
bar_index - lookback_period, level_price,
bar_index + 20, level_price,
color=color.new(resistance_color, line_alpha),
width=lwidth, style=lstyle, extend=extend.right)
if show_zones
level_obj.level_box := box.new(
bar_index - lookback_period, level_price + resist_zone_above,
bar_index + 20, level_price - resist_zone_below,
bgcolor=color.new(color.red, zone_transparency),
border_color=color.new(color.red, 80), extend=extend.right)
if show_labels
touch_text = touches > 1 ? " (" + str.tostring(touches) + "x)" : ""
level_obj.level_label := label.new(
bar_index + 25, level_price,
str.tostring(level_price, format.mintick) + touch_text,
style=label.style_label_left,
color=color.new(resistance_color, 60),
textcolor=color.white, size=size.small)
array.set(resistance_data, i, level_obj)
// Draw levels (optimized - updates positions on each bar for extend)
if barstate.islast or levels_changed
f_draw_support_levels()
f_draw_resistance_levels()
levels_changed := false
// ═══════════════════════════════════════════════════════════════════════════════
// PLOT - EMA Line
// ═══════════════════════════════════════════════════════════════════════════════
plot(show_ema ? ema_9 : na, "9 EMA", color=color.red, linewidth=2)
// ═══════════════════════════════════════════════════════════════════════════════
// PLOT - Signal Markers
// ═══════════════════════════════════════════════════════════════════════════════
// Regular buy/sell signals
plotshape(buy_signal and not is_sweep_signal and not is_breakout_retest,
"Buy Signal", shape.triangleup, location.belowbar, buy_signal_color, size=size.normal)
plotshape(sell_signal and not is_sweep_signal and not is_breakout_retest,
"Sell Signal", shape.triangledown, location.abovebar, sell_signal_color, size=size.normal)
// Liquidity sweep signals (diamond - highest priority visual)
plotshape(buy_signal and is_sweep_signal, "Sweep Buy", shape.diamond, location.belowbar, sweep_color, size=size.large)
plotshape(sell_signal and is_sweep_signal, "Sweep Sell", shape.diamond, location.abovebar, sweep_color, size=size.large)
// Breakout retest signals (circle)
plotshape(buy_signal and is_breakout_retest, "BR Buy", shape.circle, location.belowbar, color.new(buy_signal_color, 20), size=size.normal)
plotshape(sell_signal and is_breakout_retest, "BR Sell", shape.circle, location.abovebar, color.new(sell_signal_color, 20), size=size.normal)
// ═══════════════════════════════════════════════════════════════════════════════
// SIGNAL LABELS WITH ENTRY DETAILS (COMPACT & LIMITED)
// ═══════════════════════════════════════════════════════════════════════════════
// Track signal labels for cleanup
var label[] signal_labels = array.new_label()
if buy_signal and barstate.isconfirmed
conf_emoji = signal_confidence >= 4 ? "🔥" : signal_confidence >= 3 ? "⚡" : "⚠"
type_short = is_sweep_signal ? "SWEEP" : is_breakout_retest ? "B/R" : "ZONE"
label_text = conf_emoji + " BUY " + type_short + " @" + str.tostring(signal_level, format.mintick) + " | SL:" + str.tostring(suggested_sl, format.mintick)
new_label = label.new(bar_index, low - (zone_buffer * 1.5), label_text,
style=label.style_label_up, color=buy_signal_color,
textcolor=color.white, size=size.small)
array.push(signal_labels, new_label)
// Remove oldest label if exceeding max
if array.size(signal_labels) > max_signal_labels
old_label = array.shift(signal_labels)
label.delete(old_label)
if sell_signal and barstate.isconfirmed
conf_emoji = signal_confidence >= 4 ? "🔥" : signal_confidence >= 3 ? "⚡" : "⚠"
type_short = is_sweep_signal ? "SWEEP" : is_breakout_retest ? "B/R" : "ZONE"
label_text = conf_emoji + " SELL " + type_short + " @" + str.tostring(signal_level, format.mintick) + " | SL:" + str.tostring(suggested_sl, format.mintick)
new_label = label.new(bar_index, high + (zone_buffer * 1.5), label_text,
style=label.style_label_down, color=sell_signal_color,
textcolor=color.white, size=size.small)
array.push(signal_labels, new_label)
if array.size(signal_labels) > max_signal_labels
old_label = array.shift(signal_labels)
label.delete(old_label)
// ═══════════════════════════════════════════════════════════════════════════════
// INFO TABLE (ENHANCED LEGIBILITY)
// ═══════════════════════════════════════════════════════════════════════════════
// Convert text size input to Pine size constant
f_get_table_size() =>
table_text_size == "Large" ? size.large : table_text_size == "Small" ? size.small : size.normal
var table info_table = table.new(position.top_right, 2, 10, bgcolor=color.new(color.black, 20), border_width=2, border_color=color.new(color.gray, 50))
if show_info_table and barstate.islast
txt_size = f_get_table_size()
// Header with high contrast
table.cell(info_table, 0, 0, "WR-SR v2", text_color=color.white, text_size=txt_size, bgcolor=color.new(color.blue, 30), text_halign=text.align_center)
table.cell(info_table, 1, 0, "STATUS", text_color=color.white, text_size=txt_size, bgcolor=color.new(color.blue, 30), text_halign=text.align_center)
// M5 Bias with bold colors
m5_bias_text = m5_bullish ? "BULLISH ▲" : m5_bearish ? "BEARISH ▼" : "NEUTRAL ●"
m5_bias_color = m5_bullish ? color.new(color.lime, 0) : m5_bearish ? color.new(color.red, 0) : color.gray
table.cell(info_table, 0, 1, "M5 Bias", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 1, m5_bias_text, text_color=m5_bias_color, text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
// H1 Bias with bold colors
h1_bias_text = h1_bullish ? "BULLISH ▲" : h1_bearish ? "BEARISH ▼" : "NEUTRAL ●"
h1_bias_color = h1_bullish ? color.new(color.lime, 0) : h1_bearish ? color.new(color.red, 0) : color.gray
table.cell(info_table, 0, 2, "H1 Bias", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 2, h1_bias_text, text_color=h1_bias_color, text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
// Trading Direction with strong emphasis
dir_text = overall_bullish ? "LONGS ONLY" : overall_bearish ? "SHORTS ONLY" : "NO TRADE"
dir_color = overall_bullish ? color.new(color.lime, 0) : overall_bearish ? color.new(color.red, 0) : color.orange
dir_bg = overall_bullish ? color.new(color.green, 80) : overall_bearish ? color.new(color.maroon, 80) : color.new(color.gray, 80)
table.cell(info_table, 0, 3, "Direction", text_color=color.white, text_size=txt_size, bgcolor=dir_bg)
table.cell(info_table, 1, 3, dir_text, text_color=dir_color, text_size=txt_size, bgcolor=dir_bg, text_halign=text.align_right)
// CISD Status (NEW ROW)
cisd_text = "INACTIVE"
cisd_color = color.gray
if use_cisd
if cisd_bullish_signal
cisd_text := "BULLISH 🔥"
cisd_color := color.new(color.lime, 0)
else if cisd_bearish_signal
cisd_text := "BEARISH 🔥"
cisd_color := color.new(color.red, 0)
else if cisd_bull_active
cisd_text := "TRACKING ▲"
cisd_color := color.new(color.lime, 50)
else if cisd_bear_active
cisd_text := "TRACKING ▼"
cisd_color := color.new(color.red, 50)
table.cell(info_table, 0, 4, "CISD", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 4, cisd_text, text_color=cisd_color, text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
// Separator with better visibility
table.cell(info_table, 0, 5, "━━━━━", text_color=color.new(color.gray, 30), text_size=txt_size, bgcolor=color.new(color.black, 50))
table.cell(info_table, 1, 5, "━━━━━", text_color=color.new(color.gray, 30), text_size=txt_size, bgcolor=color.new(color.black, 50))
// Nearest Support with improved contrast
nearest_sup = 0.0
if array.size(support_data) > 0
// Find closest support below current price
min_dist = 99999.0
for i = 0 to array.size(support_data) - 1
s = array.get(support_data, i)
dist = close - s.price
if dist > 0 and dist < min_dist
min_dist := dist
nearest_sup := s.price
sup_text = nearest_sup > 0 ? str.tostring(nearest_sup, format.mintick) : "N/A"
table.cell(info_table, 0, 6, "Support ↓", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 6, sup_text, text_color=color.new(color.lime, 0), text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
// Nearest Resistance with improved contrast
nearest_res = 0.0
if array.size(resistance_data) > 0
min_dist = 99999.0
for i = 0 to array.size(resistance_data) - 1
r = array.get(resistance_data, i)
dist = r.price - close
if dist > 0 and dist < min_dist
min_dist := dist
nearest_res := r.price
res_text = nearest_res > 0 ? str.tostring(nearest_res, format.mintick) : "N/A"
table.cell(info_table, 0, 7, "Resist ↑", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 7, res_text, text_color=color.new(color.red, 0), text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
// Level counts with clear labels
table.cell(info_table, 0, 8, "Sup Lvls", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 8, str.tostring(array.size(support_data)), text_color=color.new(color.lime, 0), text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
table.cell(info_table, 0, 9, "Res Lvls", text_color=color.new(color.silver, 0), text_size=txt_size, bgcolor=color.new(color.black, 30))
table.cell(info_table, 1, 9, str.tostring(array.size(resistance_data)), text_color=color.new(color.red, 0), text_size=txt_size, bgcolor=color.new(color.black, 30), text_halign=text.align_right)
// ═══════════════════════════════════════════════════════════════════════════════
// ALERTS
// ═══════════════════════════════════════════════════════════════════════════════
// Zone Test Alerts
alertcondition(buy_signal and signal_type == "ZONE TEST", "Zone Test BUY",
"🟢 {{ticker}} M5: BUY SIGNAL\nType: Zone Test\nConfidence: Check chart for details")
alertcondition(sell_signal and signal_type == "ZONE TEST", "Zone Test SELL",
"🔴 {{ticker}} M5: SELL SIGNAL\nType: Zone Test\nConfidence: Check chart for details")
// Liquidity Sweep Alerts (HIGH PRIORITY)
alertcondition(buy_signal and is_sweep_signal, "⚡ Liquidity Sweep BUY",
"⚡ {{ticker}} M5: LIQUIDITY SWEEP BUY\nPrice swept below support and reclaimed\nHIGH PROBABILITY SETUP")
alertcondition(sell_signal and is_sweep_signal, "⚡ Liquidity Sweep SELL",
"⚡ {{ticker}} M5: LIQUIDITY SWEEP SELL\nPrice swept above resistance and reclaimed\nHIGH PROBABILITY SETUP")
// Breakout Retest Alerts
alertcondition(buy_signal and is_breakout_retest, "Breakout Retest BUY",
"🔄 {{ticker}} M5: BREAKOUT RETEST BUY\nBroken resistance retested as support")
alertcondition(sell_signal and is_breakout_retest, "Breakout Retest SELL",
"🔄 {{ticker}} M5: BREAKOUT RETEST SELL\nBroken support retested as resistance")
// Combined Alerts
alertcondition(buy_signal, "📈 Any BUY Signal",
"📈 {{ticker}} M5: BUY SIGNAL DETECTED\nCheck chart for entry details")
alertcondition(sell_signal, "📉 Any SELL Signal",
"📉 {{ticker}} M5: SELL SIGNAL DETECTED\nCheck chart for entry details")
// High Confidence Only
alertcondition(buy_signal and signal_confidence >= 4, "🎯 High Confidence BUY",
"🎯 {{ticker}} M5: HIGH CONFIDENCE BUY\nMultiple confluence factors aligned")
alertcondition(sell_signal and signal_confidence >= 4, "🎯 High Confidence SELL",
"🎯 {{ticker}} M5: HIGH CONFIDENCE SELL\nMultiple confluence factors aligned")
// ═══════════════════════════════════════════════════════════════════════════════
// END OF SCRIPT
// ═══════════════════════════════════════════════════════════════════════════════
Show all 944 lines