Wave Navigator is a price-chart indicator that builds zigzag swings from ATR or percentage thresholds and scans stored pivots for Elliott-style impulse, diagonal, flat and triangle patterns.
//@version=6
// ============================================================================
// Wave Navigator [WavesUnchained]
// Version: 2.0
// Author: WavesUnchained
// Build: 2025-10-13 02:06:31
// ============================================================================
// Multi-timeframe Elliott Wave detector using ZigZag swings, rule engine, weighted scoring, and persistent visualization
//
// Features:
// ✓ ZigZag-based pivot detection
// ✓ Elliott Wave pattern detection (Impulse, Diagonal, Flat, Triangle)
// ✓ Rule engine with Elliott validation
// ✓ Advanced scoring system (Momentum, Volume, Time ratio)
// ✓ Persistent visualization (LuxAlgo-style dynamic updates)
// ✓ Multi-pattern ranking (Top-N display)
// ✓ RSI/MACD momentum confirmation
// ✓ Volume-based wave strength validation
// ✓ Fibonacci retracement/extension zones
// ✓ Real-time pattern table with score breakdown
//
// ⚠️ AUTO-GENERATED - DO NOT EDIT MANUALLY!
// Generated by build.py from modules in wave-navigator/
// ============================================================================
//@version=6
indicator("Wave Navigator [WavesUnchained]", shorttitle="WN", overlay=true, max_labels_count=500, max_lines_count=500, max_boxes_count=500)
// ============================================================================
// SHARED MODULES
// ============================================================================
// ===== SHARED: HELPERS =====
// ============================================================================
// SHARED HELPERS - Universal Utility Functions (SHARED MODULE)
// ============================================================================
// @version=6
// @description: Common utility functions shared across indicators
//
// Purpose:
// ✓ Math utilities (normalization, clamping, etc.)
// ✓ Pivot detection
// ✓ Zone calculations
// ✓ Array utilities
//
// Used by: CPM, ZigZag Corridors, Pattern Pilot, Wave Analysis
//
// Integration:
// Reference in config.json: "../_shared/helpers.pine"
//
// Note: Indicator-specific logic should remain in local modules.
// Only truly generic utilities belong here.
// ============================================================================
// ==== MATH UTILITIES ====
// Calculate z-score (standardization)
// @param src: Source series
// @param len: Lookback period
// @returns Z-score
f_zscore(float src, int len) =>
float ma = ta.sma(src, len)
float sd = ta.stdev(src, len)
sd == 0.0 ? 0.0 : (src - ma) / sd
// Map value to signed range [-1, +1]
// @param x: Input value
// @param lo: Lower bound
// @param hi: Upper bound
// @returns Normalized value in [-1, +1]
f_signed(float x, float lo, float hi) =>
float cl = math.max(lo, math.min(hi, x))
(cl - lo) / (hi - lo) * 2.0 - 1.0
// Clamp value to range
// @param x: Input value
// @param min: Minimum value
// @param max: Maximum value
// @returns Clamped value
f_clamp(float x, float min, float max) =>
math.max(min, math.min(max, x))
// Normalize value to [0, 1] range
// @param x: Input value
// @param lo: Lower bound
// @param hi: Upper bound
// @returns Normalized value in [0, 1]
f_normalize(float x, float lo, float hi) =>
float rangeVal = hi - lo
rangeVal == 0.0 ? 0.5 : (x - lo) / rangeVal
// Safe division (avoid divide by zero)
// @param numerator: Numerator
// @param denominator: Denominator
// @param defaultVal: Value to return if denominator is zero
// @returns Division result or default
f_safeDivide(float numerator, float denominator, float defaultVal = 0.0) =>
denominator == 0.0 ? defaultVal : numerator / denominator
// ==== PIVOT DETECTION ====
// Generic pivot high detection
// @param src: Source series (typically high)
// @param leftBars: Bars to left of pivot
// @param rightBars: Bars to right of pivot
// @returns Pivot high price or na
pivotHigh(series float src, int leftBars, int rightBars) =>
ta.pivothigh(src, leftBars, rightBars)
// Generic pivot low detection
// @param src: Source series (typically low)
// @param leftBars: Bars to left of pivot
// @param rightBars: Bars to right of pivot
// @returns Pivot low price or na
pivotLow(series float src, int leftBars, int rightBars) =>
ta.pivotlow(src, leftBars, rightBars)
// Get last confirmed pivot (with index)
// @param src: Source series
// @param leftBars: Bars to left
// @param rightBars: Bars to right
// @param isHigh: True for pivot high, False for pivot low
// @returns [pivotPrice, pivotIndex]
f_lastPivot(series float src, int leftBars, int rightBars, bool isHigh) =>
var float lastVal = na
var int lastIdx = na
if isHigh
float ph = pivotHigh(src, leftBars, rightBars)
if not na(ph)
lastVal := ph
lastIdx := bar_index - rightBars
else
float pl = pivotLow(src, leftBars, rightBars)
if not na(pl)
lastVal := pl
lastIdx := bar_index - rightBars
[lastVal, lastIdx]
// ==== ZONE UTILITIES ====
// Calculate overlap percentage between two zones
// @param aLo: Zone A lower bound
// @param aHi: Zone A upper bound
// @param bLo: Zone B lower bound
// @param bHi: Zone B upper bound
// @returns Overlap percentage (0-100)
f_overlap(float aLo, float aHi, float bLo, float bHi) =>
float lo = math.max(aLo, bLo)
float hi = math.min(aHi, bHi)
float inter = math.max(0.0, hi - lo)
float base = math.max(aHi - aLo, bHi - bLo)
base > 0 ? inter / base * 100.0 : 0.0
// Calculate proximity to level (ATR-normalized)
// @param price: Current price
// @param level: Target level
// @param atr: ATR value
// @returns Distance in ATR units
f_proximity(float price, float level, float atr) =>
atr > 0 ? math.abs(price - level) / atr : 10.0
// Check if price is within zone
// @param price: Price to check
// @param zoneLo: Zone lower bound
// @param zoneHi: Zone upper bound
// @returns True if price in zone
f_inZone(float price, float zoneLo, float zoneHi) =>
price >= zoneLo and price <= zoneHi
// ==== OSCILLATOR UTILITIES ====
// RSI z-score
// @param len: RSI length
// @param zscore_len: Z-score lookback
// @returns RSI z-score
f_rsi_zscore(int len, int zscore_len) =>
float rsi = ta.rsi(close, len)
f_zscore(rsi, zscore_len)
// ==== ARRAY UTILITIES ====
// Safe array get with default value
// @param arr: Float array
// @param idx: Index
// @param defaultVal: Default if index invalid
// @returns Array value or default
f_get_float(array<float> arr, int idx, float defaultVal) =>
idx >= 0 and idx < array.size(arr) ? array.get(arr, idx) : defaultVal
// Safe array get with default value (int)
// @param arr: Int array
// @param idx: Index
// @param defaultVal: Default if index invalid
// @returns Array value or default
f_get_int(array<int> arr, int idx, int defaultVal) =>
idx >= 0 and idx < array.size(arr) ? array.get(arr, idx) : defaultVal
// ==== TIMEFRAME UTILITIES ====
// Check if current bar is start of new session
// @returns True if new session started
f_isNewSession() =>
ta.change(time("D"))
// Check if current bar is start of new week
// @returns True if new week started
f_isNewWeek() =>
ta.change(time("W"))
// Check if current bar is start of new month
// @returns True if new month started
f_isNewMonth() =>
ta.change(time("M"))
// ==== VOLATILITY UTILITIES ====
// (Volatility functions moved to _shared/indicators.pine)
// ==== TREND UTILITIES ====
// Calculate simple slope
// @param src: Source series
// @param len: Lookback length
// @returns Slope (change per bar)
f_slope(series float src, int len) =>
(src - src[len]) / len
// Check if series is rising
// @param src: Source series
// @param len: Lookback length
// @returns True if rising
f_isRising(series float src, int len) =>
ta.rising(src, len)
// Check if series is falling
// @param src: Source series
// @param len: Lookback length
// @returns True if falling
f_isFalling(series float src, int len) =>
ta.falling(src, len)
// ==== VOLUME UTILITIES ====
// Volume strength score (0-1 scale)
// @param length: Volume MA period
// @returns Strength score based on volume ratio
f_volumeStrength(int length = 20) =>
float volMA = ta.sma(volume, length)
float ratio = volMA > 0 ? volume / volMA : 1.0
// 0.0 = below 50% of MA, 0.5 = at MA, 1.0 = 2x MA or more
math.min(ratio / 2.0, 1.0)
// Volume confirmation check
// @param length: Volume MA period
// @param threshold: Minimum ratio threshold (default 1.5x MA)
// @returns Boolean confirmation
f_volumeConfirmation(int length = 20, float threshold = 1.5) =>
float volMA = ta.sma(volume, length)
float ratio = volMA > 0 ? volume / volMA : 1.0
ratio >= threshold
// Relative volume compared to specific bars
// @param barsAgo: Bars to compare to
// @returns Volume ratio vs previous bar
f_volumeRelative(int barsAgo = 1) =>
float prevVol = nz(volume[barsAgo], volume)
prevVol > 0 ? volume / prevVol : 1.0
// Volume spike detection
// @param length: Volume MA period
// @param spikeMultiplier: Multiplier for spike (default 2.0)
// @returns True if volume spike detected
f_volumeSpike(int length = 20, float spikeMultiplier = 2.0) =>
float volMA = ta.sma(volume, length)
float ratio = volMA > 0 ? volume / volMA : 1.0
ratio >= spikeMultiplier
// Average volume over period
// @param length: Period length
// @returns Average volume
f_volumeAvg(int length = 20) =>
ta.sma(volume, length)
// ==== MOMENTUM UTILITIES ====
// Get momentum at historical bar
// @param momentumSeries: Momentum series (RSI, MACD, etc.)
// @param barsAgo: Bars back to check
// @returns Momentum value at that bar
f_momentumAtBar(series float momentumSeries, int barsAgo) =>
barsAgo >= 0 ? momentumSeries[barsAgo] : na
// Check momentum directional confirmation
// @param momentum1: First momentum value (earlier)
// @param momentum2: Second momentum value (recent)
// @param direction: Expected direction ("UP" or "DOWN")
// @param momentumType: Type ("RSI", "MACD")
// @returns Confirmation score (0-100)
f_momentumConfirmation(float momentum1, float momentum2, string direction, string momentumType = "RSI") =>
float score = 50.0 // Neutral
if not na(momentum1) and not na(momentum2)
if direction == "UP"
if momentumType == "RSI"
// RSI should be increasing and above 50
score := (momentum2 > momentum1 and momentum2 > 50) ? 100.0 :
(momentum2 > momentum1) ? 75.0 :
(momentum2 > 50) ? 60.0 : 40.0
else if momentumType == "MACD"
// MACD should be increasing and positive
score := (momentum2 > momentum1 and momentum2 > 0) ? 100.0 :
(momentum2 > momentum1) ? 75.0 :
(momentum2 > 0) ? 60.0 : 40.0
else if direction == "DOWN"
if momentumType == "RSI"
// RSI should be decreasing and below 50
score := (momentum2 < momentum1 and momentum2 < 50) ? 100.0 :
(momentum2 < momentum1) ? 75.0 :
(momentum2 < 50) ? 60.0 : 40.0
else if momentumType == "MACD"
// MACD should be decreasing and negative
score := (momentum2 < momentum1 and momentum2 < 0) ? 100.0 :
(momentum2 < momentum1) ? 75.0 :
(momentum2 < 0) ? 60.0 : 40.0
score
// Check momentum divergence from price
// @param priceChange: Price change (positive or negative)
// @param momentumChange: Momentum change
// @returns [isDivergent, strength]
f_momentumDivergence(float priceChange, float momentumChange) =>
bool isDivergent = (priceChange > 0 and momentumChange < 0) or
(priceChange < 0 and momentumChange > 0)
float strength = isDivergent ? math.abs(priceChange - momentumChange) : 0.0
[isDivergent, strength]
// Calculate momentum strength score
// @param momentum: Current momentum value
// @param momentumType: Type ("RSI", "MACD")
// @returns Strength score (0-1)
f_momentumStrength(float momentum, string momentumType = "RSI") =>
float strength = 0.5 // Neutral
if momentumType == "RSI"
// RSI 0-100 -> normalize to 0-1
strength := momentum / 100.0
else if momentumType == "MACD"
// MACD is unbounded, use normalization
strength := na(momentum) ? 0.5 : math.min(math.abs(momentum) / 10.0, 1.0)
strength
// ==== PRICE ACTION UTILITIES ====
// Check if current bar is bullish
// @returns True if close > open
f_isBullBar() =>
close > open
// Check if current bar is bearish
// @returns True if close < open
f_isBearBar() =>
close < open
// Calculate bar body size
// @returns Absolute body size
f_bodySize() =>
math.abs(close - open)
// Calculate upper wick size
// @returns Upper wick size
f_upperWick() =>
high - math.max(open, close)
// Calculate lower wick size
// @returns Lower wick size
f_lowerWick() =>
math.min(open, close) - low
// Body as percentage of total range
// @returns Body percentage (0-1)
f_bodyRatio() =>
float rangeVal = high - low
rangeVal > 0 ? f_bodySize() / rangeVal : 0.0
// ============================================================================
// END OF SHARED HELPERS MODULE
// ============================================================================
// Available Functions:
// Math: f_zscore, f_signed, f_clamp, f_normalize, f_safeDivide
// Pivots: pivotHigh, pivotLow, f_lastPivot
// Zones: f_overlap, f_proximity, f_inZone
// Oscillators: f_rsi_zscore
// Arrays: f_get_float, f_get_int
// Timeframe: f_isNewSession, f_isNewWeek, f_isNewMonth
// Volatility: (moved to indicators.pine)
// Trend: f_slope, f_isRising, f_isFalling
// Volume: f_volumeStrength, f_volumeConfirmation,
// f_volumeRelative, f_volumeSpike, f_volumeAvg
// Momentum: f_momentumAtBar, f_momentumConfirmation, f_momentumDivergence,
// f_momentumStrength
// Price Action: f_isBullBar, f_isBearBar, f_bodySize, f_upperWick,
// f_lowerWick, f_bodyRatio
//
// Usage Example:
// // Z-score normalization
// float rsiZ = f_zscore(ta.rsi(close, 14), 20)
//
// // Pivot detection
// [pivotPrice, pivotBar] = f_lastPivot(high, 5, 5, true)
//
// // Zone overlap
// float overlap = f_overlap(1.2000, 1.2050, 1.2030, 1.2080) // 40%
// ============================================================================
// ===== SHARED: TYPES =====
// ============================================================================
// SHARED TYPES - Generic Data Structures (SHARED MODULE)
// ============================================================================
// @version=6
// @description: Common type definitions shared across indicators
//
// Purpose:
// ✓ Define generic, reusable data structures
// ✓ Ensure consistency across indicators
// ✓ Reduce duplicate type definitions
//
// Used by: CPM, ZigZag Corridors, Pattern Pilot, Wave Analysis
//
// Integration:
// Reference in config.json: "../_shared/types.pine"
//
// Note: Indicator-specific types should remain in local modules.
// Only truly generic types belong here.
// ============================================================================
// ==== TF BLOCK (CPM/ZZC Multi-Timeframe Scoring) ====
// Timeframe analysis block for CPM-style multi-category scoring
// Used by: CPM, ZZC (multi-TF mode), any MTF dashboard
type TFBlock
float scoreFlow // Money flow score (-3 to +3)
float scoreMom // Momentum score (-3 to +3)
float scoreTrend // Trend score (-3 to +3)
float scoreVol // Volatility score (negative = high risk)
float scoreStruct // Market structure score
float scoreDiv // Divergence score
float scoreTotal // Weighted total score
bool divUp // Bullish divergence detected
bool divDn // Bearish divergence detected
string badge // Special condition badges (OB/OS/Div/etc)
// ==== SCORE BLOCK (Generic Scoring Container) ====
// Generic score block for any multi-factor scoring system
// Can be used for: TF analysis, feature scoring, signal strength, etc.
type ScoreBlock
float score1 // Factor 1 score
float score2 // Factor 2 score
float score3 // Factor 3 score
float score4 // Factor 4 score
float score5 // Factor 5 score
float score6 // Factor 6 score
float scoreTotal // Weighted total
bool flagUp // Bullish flag (divergence, pattern, etc.)
bool flagDn // Bearish flag
string badge // Status badge (OB/OS/Div/Strong/Weak/etc.)
// ==== ZONE (Generic Zone Definition) ====
// Generic zone/level definition for support, resistance, reversal zones, etc.
type Zone
float upper // Upper boundary
float lower // Lower boundary
float center // Center/pivot level
int startBar // Bar where zone begins
int endBar // Bar where zone ends (or na for ongoing)
bool isActive // Is zone currently active
string zoneType // Type: "support", "resistance", "reversal", "consolidation"
color zoneColor // Visualization color
int touchCount // Number of times zone was tested
// ==== LEG INFO (Generic Trend Leg) ====
// Generic trend leg/swing information
// Can represent: ZigZag leg, Elliott wave leg, trend swing, etc.
type LegInfo
int startIdx // Starting bar index
float startPrice // Starting price
int endIdx // Ending bar index
float endPrice // Ending price
bool isUp // True = upward leg, False = downward leg
float moveAbs // Absolute move (points)
float movePct // Percentage move
int duration // Bars in leg
float avgSlope // Average slope
bool isValid // Validation flag
// ==== SIGNAL METADATA (Generic Signal Container) ====
// Generic signal metadata for any signal system
type SignalMeta
int signalType // Signal type (e.g., -2 to +2, or custom enum)
float confidence // Confidence level (0-100%)
float strength // Signal strength score
string direction // "BULL", "BEAR", "NEUTRAL"
string quality // "STRONG", "MODERATE", "WEAK"
bool isValid // Validation flag
int barIndex // Bar where signal generated
string reason // Reason/description
// ==== PIVOT POINT (Price Pivot) ====
// Generic pivot point (high/low)
// Used by: ZZC, Pattern Pilot, Wave Pilot
type Pivot
int barIndex // Bar index of pivot
float price // Pivot price level
bool isHigh // True = pivot high, False = pivot low
// Extended pivot with strength
type PivotPoint
int barIndex // Bar index of pivot
float price // Pivot price level
bool isHigh // True = pivot high, False = pivot low
float strength // Pivot strength (optional, 0-1)
bool isConfirmed // Is pivot confirmed (past lookback period)
// ==== ENVELOPE STATE (Channel/Band State) ====
// Generic envelope/channel state
// Works for: Keltner, Bollinger, Donchian, ATR bands, etc.
type EnvelopeState
float upper // Upper band
float lower // Lower band
float basis // Middle/basis line
float width // Band width (absolute)
float widthPct // Band width (percentage of price)
bool isExpanded // Is envelope expanded (volatility high)
bool isCompressed // Is envelope compressed (volatility low)
string envelopeType // Type: "KC", "BB", "DC", "ATR"
// ==== DIVERGENCE INFO ====
// Generic divergence information
type DivergenceInfo
bool isBullish // Bullish divergence (price LL, osc HL)
bool isBearish // Bearish divergence (price HH, osc LH)
bool isHidden // Hidden divergence (trend continuation)
bool isRegular // Regular divergence (trend reversal)
float strength // Divergence strength (0-1)
int pivotBar // Bar where divergence pivot occurred
string oscillator // Oscillator used: "RSI", "MACD", "MFI", etc.
// ==== MARKET STATE ====
// Generic market state/regime information
type MarketState
string trend // "UPTREND", "DOWNTREND", "SIDEWAYS"
string phase // "EXPANSION", "CONSOLIDATION", "REVERSAL"
float volatility // Volatility measure (ATR%, IV, etc.)
float momentum // Momentum measure (RSI, ROC, etc.)
bool isTrending // ADX-based trending flag
bool isOverextended // Overbought/oversold flag
string timeframe // Timeframe of this state
// ============================================================================
// END OF SHARED TYPES MODULE
// ============================================================================
// Available Types:
// ✓ ScoreBlock: Multi-factor scoring (10 fields)
// ✓ Zone: Generic zone/level (9 fields)
// ✓ LegInfo: Trend leg/swing (10 fields)
// ✓ SignalMeta: Signal metadata (8 fields)
// ✓ PivotPoint: Price pivot (5 fields)
// ✓ EnvelopeState: Channel/band state (8 fields)
// ✓ DivergenceInfo: Divergence information (7 fields)
// ✓ MarketState: Market regime (7 fields)
//
// Usage Example:
// // Create a zone
// Zone supZone = Zone.new()
// supZone.upper := 1.2050
// supZone.lower := 1.2000
// supZone.zoneType := "support"
//
// // Create signal metadata
// SignalMeta sig = SignalMeta.new()
// sig.signalType := 2 // Strong bull
// sig.confidence := 85.0
// sig.direction := "BULL"
// ============================================================================
// ===== SHARED: INDICATORS =====
// ============================================================================
// SHARED TECHNICAL INDICATORS - Universal Indicator Wrappers (SHARED MODULE)
// ============================================================================
// @version=6
// @description: Standard technical indicator wrappers with HTF support
//
// Purpose:
// ✓ Consistent indicator calculation across all indicators
// ✓ Higher Timeframe (HTF) support for all indicators
// ✓ Reduced code duplication
// ✓ Used by: CPM, ZZC, Pattern Pilot, Wave Analysis
//
// Categories:
// ✓ Moving Averages (EMA, SMA, SMMA, WMA)
// ✓ Oscillators (RSI, MACD, Stochastic, CCI, MFI)
// ✓ Volatility (ATR, Bollinger Bands, Keltner Channels)
// ✓ Trend (ADX, DMI, Parabolic SAR)
// ✓ Volume (OBV, Volume MA, VWAP)
//
// Integration:
// Reference in config.json: "../_shared/indicators.pine"
// ============================================================================
// ==== MOVING AVERAGES ====
// Exponential Moving Average
// @param length: Period length
// @returns EMA value
f_ema(int length) =>
ta.ema(close, length)
// Simple Moving Average
// @param length: Period length
// @returns SMA value
f_sma(int length) =>
ta.sma(close, length)
// Smoothed Moving Average (SMMA / RMA)
// @param length: Period length
// @returns SMMA value
f_smma(int length) =>
ta.rma(close, length)
// Weighted Moving Average
// @param length: Period length
// @returns WMA value
f_wma(int length) =>
ta.wma(close, length)
// ==== OSCILLATORS ====
// Relative Strength Index
// @param length: RSI period
// @returns RSI value (0-100)
f_rsi(int length) =>
ta.rsi(close, length)
// MACD (Moving Average Convergence Divergence)
// @param fastLen: Fast EMA length
// @param slowLen: Slow EMA length
// @param signalLen: Signal line length
// @returns [macdLine, signalLine, histogram]
f_macd(int fastLen = 12, int slowLen = 26, int signalLen = 9) =>
ta.macd(close, fastLen, slowLen, signalLen)
// MACD Histogram only
// @returns MACD histogram (macdLine - signalLine)
f_macd_hist() =>
[m, s, _] = ta.macd(close, 12, 26, 9)
m - s
// Stochastic Oscillator
// @param kLen: %K period
// @param kSmooth: %K smoothing
// @param dLen: %D period
// @returns [k, d]
f_stoch(int kLen = 14, int kSmooth = 1, int dLen = 3) =>
ta.stoch(close, high, low, kLen)
// Stochastic RSI
// @param rsiLen: RSI period
// @param stochLen: Stochastic period
// @param kSmooth: %K smoothing
// @param dSmooth: %D smoothing
// @returns [k, d]
f_stochRSI(int rsiLen = 14, int stochLen = 14, int kSmooth = 3, int dSmooth = 3) =>
float rsi = ta.rsi(close, rsiLen)
float lowestRSI = ta.lowest(rsi, stochLen)
float highestRSI = ta.highest(rsi, stochLen)
float stochRSI = highestRSI - lowestRSI != 0 ? (rsi - lowestRSI) / (highestRSI - lowestRSI) * 100 : 0
float k = ta.sma(stochRSI, kSmooth)
float d = ta.sma(k, dSmooth)
[k, d]
// Commodity Channel Index
// @param length: CCI period
// @returns CCI value
f_cci(int length = 20) =>
ta.cci(close, length)
// Money Flow Index
// @param length: MFI period
// @returns MFI value (0-100)
f_mfi(int length = 14) =>
ta.mfi(close, length)
// Rate of Change
// @param length: ROC period
// @returns ROC percentage
f_roc(int length = 9) =>
ta.roc(close, length)
// ==== VOLATILITY INDICATORS ====
// Average True Range
// @param length: ATR period
// @param multiplier: ATR multiplier (default 1.0)
// @returns ATR value
f_atr(int length = 14, float multiplier = 1.0) =>
ta.atr(length) * multiplier
// ATR as percentage of price
// @param length: ATR period
// @returns ATR percentage
f_atrPercent(int length = 14) =>
ta.atr(length) / close * 100.0
// True Range as percentage
// @returns TR percentage
f_trPercent() =>
ta.tr / close * 100.0
// Bollinger Bands
// @param length: BB period
// @param mult: Standard deviation multiplier
// @returns [upper, basis, lower]
f_bollinger(int length = 20, float mult = 2.0) =>
float basis = ta.sma(close, length)
float dev = ta.stdev(close, length)
[basis + dev * mult, basis, basis - dev * mult]
// Keltner Channels
// @param length: EMA period
// @param atrLen: ATR period
// @param mult: ATR multiplier
// @returns [upper, basis, lower]
f_keltner(int length = 20, int atrLen = 10, float mult = 2.0) =>
float basis = ta.ema(close, length)
float atrVal = ta.atr(atrLen)
[basis + atrVal * mult, basis, basis - atrVal * mult]
// Donchian Channels
// @param length: Period
// @returns [upper, basis, lower]
f_donchian(int length = 20) =>
float upper = ta.highest(high, length)
float lower = ta.lowest(low, length)
float basis = (upper + lower) / 2.0
[upper, basis, lower]
// ==== TREND INDICATORS ====
// Average Directional Index
// @param length: ADX period
// @returns ADX value
f_adx(int length = 14) =>
[_, _, adx] = ta.dmi(length, length)
adx
// Directional Movement Index
// @param length: DMI period
// @returns [plusDI, minusDI, adx]
f_dmi(int length = 14) =>
ta.dmi(length, length)
// Parabolic SAR
// @param start: Initial acceleration factor
// @param inc: Increment
// @param max: Maximum acceleration
// @returns SAR value
f_sar(float start = 0.02, float inc = 0.02, float max = 0.2) =>
ta.sar(start, inc, max)
// ==== VOLUME INDICATORS ====
// On Balance Volume
// @returns OBV value
f_obv() =>
ta.obv
// Volume Moving Average
// @param length: Volume MA period
// @returns Volume MA
f_volumeMA(int length = 20) =>
ta.sma(volume, length)
// Volume ratio (current vs MA)
// @param length: Volume MA period
// @returns Volume ratio
f_volumeRatio(int length = 20) =>
float volMA = ta.sma(volume, length)
volMA > 0 ? volume / volMA : 1.0
// VWAP (Volume Weighted Average Price)
// @returns VWAP value
f_vwap() =>
ta.vwap(close)
// ==== HIGHER TIMEFRAME (HTF) WRAPPERS ====
// Generic HTF wrapper for any value
// @param value: Value to request from HTF
// @param tf: Timeframe string (e.g., "D", "60", "240")
// @returns HTF value
f_htf(series float value, simple string tf) =>
request.security(syminfo.tickerid, tf, value,
gaps=barmerge.gaps_off, lookahead=barmerge.lookahead_off)
// HTF EMA
// @param length: EMA period
// @param tf: Timeframe
// @returns HTF EMA value
f_ema_htf(int length, simple string tf) =>
f_htf(f_ema(length), tf)
// HTF SMA
// @param length: SMA period
// @param tf: Timeframe
// @returns HTF SMA value
f_sma_htf(int length, simple string tf) =>
f_htf(f_sma(length), tf)
// HTF RSI
// @param length: RSI period
// @param tf: Timeframe
// @returns HTF RSI value
f_rsi_htf(int length, simple string tf) =>
f_htf(f_rsi(length), tf)
// HTF MACD
// @param tf: Timeframe
// @returns [macdLine, signalLine, histogram]
f_macd_htf(simple string tf) =>
[m, s, h] = f_macd()
[f_htf(m, tf), f_htf(s, tf), f_htf(h, tf)]
// HTF ATR
// @param length: ATR period
// @param tf: Timeframe
// @returns HTF ATR value
f_atr_htf(int length, simple string tf) =>
f_htf(f_atr(length), tf)
// HTF ADX
// @param length: ADX period
// @param tf: Timeframe
// @returns HTF ADX value
f_adx_htf(int length, simple string tf) =>
f_htf(f_adx(length), tf)
// ==== HELPER FUNCTIONS ====
// Check if oscillator is overbought
// @param osc: Oscillator value
// @param threshold: Overbought threshold (default 70)
// @returns Boolean
f_isOverbought(float osc, float threshold = 70.0) =>
osc > threshold
// Check if oscillator is oversold
// @param osc: Oscillator value
// @param threshold: Oversold threshold (default 30)
// @returns Boolean
f_isOversold(float osc, float threshold = 30.0) =>
osc < threshold
// Normalize oscillator to 0-1 range
// @param osc: Oscillator value
// @param min: Minimum value (default 0)
// @param max: Maximum value (default 100)
// @returns Normalized value (0-1)
f_normalizeOsc(float osc, float min = 0.0, float max = 100.0) =>
float rangeVal = max - min
rangeVal > 0 ? (osc - min) / rangeVal : 0.5
// ============================================================================
// END OF SHARED INDICATORS MODULE
// ============================================================================
// Available Functions:
// Moving Averages: f_ema, f_sma, f_smma, f_wma
// Oscillators: f_rsi, f_macd, f_macd_hist, f_stoch, f_stochRSI, f_cci, f_mfi, f_roc
// Volatility: f_atr, f_atrPercent, f_trPercent, f_bollinger, f_keltner, f_donchian
// Trend: f_adx, f_dmi, f_sar
// Volume: f_obv, f_volumeMA, f_volumeRatio, f_vwap
// HTF: f_htf, f_ema_htf, f_sma_htf, f_rsi_htf, f_macd_htf, f_atr_htf, f_adx_htf
// Helpers: f_isOverbought, f_isOversold, f_normalizeOsc
//
// Usage Example:
// // Standard indicators
// float rsi = f_rsi(14)
// [macd, signal, hist] = f_macd()
// float atr = f_atr(14, 2.0)
//
// // HTF indicators
// float rsiDaily = f_rsi_htf(14, "D")
// float ema200_4H = f_ema_htf(200, "240")
//
// // Volume analysis
// float volRatio = f_volumeRatio(20)
// bool highVolume = volRatio > 1.5
//
// // Overbought/Oversold
// bool ob = f_isOverbought(rsi, 70)
// bool os = f_isOversold(rsi, 30)
// ============================================================================
// ===== SHARED: DIVERGENCE =====
// ============================================================================
// SHARED DIVERGENCE DETECTION - Universal Divergence Analysis (SHARED MODULE)
// ============================================================================
// @version=6
// @description: Comprehensive divergence detection for oscillators
//
// Purpose:
// ✓ Regular divergence (trend reversal signals)
// ✓ Hidden divergence (trend continuation signals)
// ✓ Both series-based and array-based implementations
// ✓ Used by: CPM, Pattern Pilot, ZZC, Wave Analysis
//
// Divergence Types:
// Regular Bullish: Price LL + Oscillator HL (reversal up)
// Regular Bearish: Price HH + Oscillator LH (reversal down)
// Hidden Bullish: Price HL + Oscillator LL (continuation up)
// Hidden Bearish: Price LH + Oscillator HH (continuation down)
//
// Integration:
// Reference in config.json: "../_shared/divergence.pine"
//
// Note: Uses centralized pivot detection from helpers.pine
// ============================================================================
// ==== HELPER: LAST TWO PIVOTS ====
// Track last two confirmed pivots for divergence comparison
// @param s: Source series (price or oscillator)
// @param l: Left bars for pivot
// @param r: Right bars for pivot
// @param isHigh: true for highs, false for lows
// @returns [val1, idx1, val2, idx2]
f_last_two_pivots(series float s, int l, int r, bool isHigh) =>
var float v1 = na
var float v2 = na
var int i1 = na
var int i2 = na
float piv = isHigh ? ta.pivothigh(s, l, r) : ta.pivotlow(s, l, r)
if not na(piv)
// Shift old pivot to slot 2
v2 := v1
i2 := i1
// Store new pivot in slot 1
v1 := piv
i1 := bar_index - r
[v1, i1, v2, i2]
// ==== REGULAR DIVERGENCE (SERIES-BASED) ====
// Detect regular divergence using series data (CPM-style)
// Best for: Real-time detection, inline processing
// @param oscSeries: Oscillator series (RSI, MACD, etc.)
// @param leftBars: Left lookback for pivot
// @param rightBars: Right lookback for pivot
// @returns [bullishDiv, bearishDiv]
f_divergence_regular_series(series float oscSeries, int leftBars, int rightBars) =>
// Get price pivots
[pH1, iPH1, pH2, iPH2] = f_last_two_pivots(high, leftBars, rightBars, true)
[pL1, iPL1, pL2, iPL2] = f_last_two_pivots(low, leftBars, rightBars, false)
// Get oscillator pivots
[oH1, iOH1, oH2, iOH2] = f_last_two_pivots(oscSeries, leftBars, rightBars, true)
[oL1, iOL1, oL2, iOL2] = f_last_two_pivots(oscSeries, leftBars, rightBars, false)
// Check validity
bool okH = not na(iPH1) and not na(iPH2) and not na(iOH1) and not na(iOH2)
bool okL = not na(iPL1) and not na(iPL2) and not na(iOL1) and not na(iOL2)
// Regular Bearish: Price HH + Osc LH
bool regBear = okH and (pH1 > pH2) and (oH1 < oH2)
// Regular Bullish: Price LL + Osc HL
bool regBull = okL and (pL1 < pL2) and (oL1 > oL2)
[regBull, regBear]
// ==== REGULAR DIVERGENCE (ARRAY-BASED) ====
// Detect regular divergence using pre-computed pivot arrays (Pattern Pilot-style)
// Best for: Post-processing, batch analysis
// @param pricePivots: Array of price pivots (most recent last)
// @param oscPivots: Array of oscillator pivots (same size)
// @param isHigh: true for highs (bearish div), false for lows (bullish div)
// @returns Boolean divergence flag
f_divergence_regular_array(array<float> pricePivots, array<float> oscPivots, bool isHigh) =>
bool hasDivergence = false
if array.size(pricePivots) >= 2 and array.size(oscPivots) >= 2
float pivot1 = array.get(pricePivots, array.size(pricePivots) - 2)
float pivot2 = array.get(pricePivots, array.size(pricePivots) - 1)
float ind1 = array.get(oscPivots, array.size(oscPivots) - 2)
float ind2 = array.get(oscPivots, array.size(oscPivots) - 1)
if isHigh
// Bearish: Price HH, Indicator LH
hasDivergence := pivot2 > pivot1 and ind2 < ind1
else
// Bullish: Price LL, Indicator HL
hasDivergence := pivot2 < pivot1 and ind2 > ind1
hasDivergence
// ==== HIDDEN DIVERGENCE (ARRAY-BASED) ====
// Detect hidden divergence (trend continuation)
// @param pricePivots: Array of price pivots
// @param oscPivots: Array of oscillator pivots
// @param isHigh: true for highs (bullish hidden), false for lows (bearish hidden)
// @returns Boolean divergence flag
f_divergence_hidden_array(array<float> pricePivots, array<float> oscPivots, bool isHigh) =>
bool hasDivergence = false
if array.size(pricePivots) >= 2 and array.size(oscPivots) >= 2
float pivot1 = array.get(pricePivots, array.size(pricePivots) - 2)
float pivot2 = array.get(pricePivots, array.size(pricePivots) - 1)
float ind1 = array.get(oscPivots, array.size(oscPivots) - 2)
float ind2 = array.get(oscPivots, array.size(oscPivots) - 1)
if isHigh
// Bullish Hidden: Price LH, Indicator HH (uptrend continuation)
hasDivergence := pivot2 < pivot1 and ind2 > ind1
else
// Bearish Hidden: Price HL, Indicator LL (downtrend continuation)
hasDivergence := pivot2 > pivot1 and ind2 < ind1
hasDivergence
// ==== HIDDEN DIVERGENCE (SERIES-BASED) ====
// Detect hidden divergence using series data
// @param oscSeries: Oscillator series
// @param leftBars: Left lookback
// @param rightBars: Right lookback
// @returns [bullishHidden, bearishHidden]
f_divergence_hidden_series(series float oscSeries, int leftBars, int rightBars) =>
// Get price pivots
[pH1, iPH1, pH2, iPH2] = f_last_two_pivots(high, leftBars, rightBars, true)
[pL1, iPL1, pL2, iPL2] = f_last_two_pivots(low, leftBars, rightBars, false)
// Get oscillator pivots
[oH1, iOH1, oH2, iOH2] = f_last_two_pivots(oscSeries, leftBars, rightBars, true)
[oL1, iOL1, oL2, iOL2] = f_last_two_pivots(oscSeries, leftBars, rightBars, false)
// Check validity
bool okH = not na(iPH1) and not na(iPH2) and not na(iOH1) and not na(iOH2)
bool okL = not na(iPL1) and not na(iPL2) and not na(iOL1) and not na(iOL2)
// Bullish Hidden: Price LH + Osc HH (uptrend continuation)
bool hidBull = okH and (pH1 < pH2) and (oH1 > oH2)
// Bearish Hidden: Price HL + Osc LL (downtrend continuation)
bool hidBear = okL and (pL1 > pL2) and (oL1 < oL2)
[hidBull, hidBear]
// ==== UNIVERSAL DIVERGENCE DETECTOR ====
// Comprehensive divergence detection with all types
// @param oscSeries: Oscillator series
// @param leftBars: Left lookback
// @param rightBars: Right lookback
// @returns [regBull, regBear, hidBull, hidBear]
f_detect_basic_divergences(series float oscSeries, int leftBars, int rightBars) =>
[regBull, regBear] = f_divergence_regular_series(oscSeries, leftBars, rightBars)
[hidBull, hidBear] = f_divergence_hidden_series(oscSeries, leftBars, rightBars)
[regBull, regBear, hidBull, hidBear]
// ==== DIVERGENCE STRENGTH CALCULATOR ====
// Calculate divergence strength based on price/oscillator gap
// @param priceVal1: First price pivot
// @param priceVal2: Second price pivot
// @param oscVal1: First oscillator pivot
// @param oscVal2: Second oscillator pivot
// @returns Strength score (0-1)
f_divergence_strength(float priceVal1, float priceVal2, float oscVal1, float oscVal2) =>
float priceChange = math.abs(priceVal2 - priceVal1)
float oscChange = math.abs(oscVal2 - oscVal1)
// Normalize to percentage
float pricePct = priceVal1 != 0 ? priceChange / math.abs(priceVal1) * 100 : 0
float oscPct = oscVal1 != 0 ? oscChange / math.abs(oscVal1) * 100 : 0
// Strength = divergence gap
float strength = math.abs(pricePct - oscPct)
// Normalize to 0-1 (cap at 20% difference = strength 1.0)
math.min(strength / 20.0, 1.0)
// ============================================================================
// END OF SHARED DIVERGENCE MODULE
// ============================================================================
// Available Functions:
// Series-based:
// - f_divergence_regular_series(osc, left, right) => [bull, bear]
// - f_divergence_hidden_series(osc, left, right) => [bull, bear]
// - f_detect_basic_divergences(osc, left, right) => [regBull, regBear, hidBull, hidBear]
//
// Array-based:
// - f_divergence_regular_array(prices, oscs, isHigh) => bool
// - f_divergence_hidden_array(prices, oscs, isHigh) => bool
//
// Utilities:
// - f_divergence_strength(pv1, pv2, ov1, ov2) => float (0-1)
// - f_last_two_pivots(series, left, right, isHigh) => [v1, i1, v2, i2]
//
// Usage Example (Series-based):
// float rsi = ta.rsi(close, 14)
// [regBull, regBear, hidBull, hidBear] = f_detect_basic_divergences(rsi, 5, 5)
// if regBull
// // Bullish reversal setup
//
// Usage Example (Array-based):
// array<float> priceHighs = array.new_float()
// array<float> rsiHighs = array.new_float()
// // ... populate arrays ...
// bool bearishDiv = f_divergence_regular_array(priceHighs, rsiHighs, true)
// ============================================================================
// ============================================================================
// PROJECT MODULES
// ============================================================================
// ===== MODULE: TYPES =====
// 3) STORAGE ARRAYS & STATE
// ───────────────────────────────────────────────────────────────────────────
var array<float> pivPrice = array.new_float()
var array<int> pivIndex = array.new_int()
var array<int> pivDir = array.new_int() // +1 high, -1 low
var array<float> pivTime = array.new_float()
var array<float> swingAmp = array.new_float()
var array<int> swingBars = array.new_int()
var array<float> swingRetrace= array.new_float()
var array<float> swingExt = array.new_float()
var float lastExtreme = na
var int lastExtremeIdx= na
var int currentDir = 0
var float trendStart = na
var int trendStartIdx = na
var array<line> zigzagLines = array.new_line()
// Pattern Candidate Type
type PatternCandidate
string patternType
int startIdx
int endIdx
int pivotCount
bool isValid
string direction
array<string> violations
float confidence
float structureScore
float retracementScore
float extensionScore
float overlapScore
float timeScore
float momentumScore
var array<PatternCandidate> detectedPatterns = array.new<PatternCandidate>(0)
// Persistent Wave Drawing Types (Sprint 5: Dynamic Visualization)
// ───────────────────────────────────────────────────────────────────────────
type WaveDrawing
// Wave lines (0→1, 1→2, 2→3, 3→4, 4→5)
line l01
line l12
line l23
line l34
line l45
// Wave labels (0, 1, 2, 3, 4, 5)
label b0
label b1
label b2
label b3
label b4
label b5
// Projection lines (min, ideal, max)
line projMin
line projIdeal
line projMax
label projMinLabel
label projIdealLabel
label projMaxLabel
// Metadata
bool active
int patternIndex
string patternType
type CorrectionDrawing
// Correction lines (A, B, C)
line lAB
line lBC
line lCD
// Correction labels (A, B, C, D)
label bA
label bB
label bC
label bD
// Wave C Projection lines (min, ideal, max)
line projMin
line projIdeal
line projMax
label projMinLabel
label projIdealLabel
label projMaxLabel
// Metadata
bool active
int patternIndex
// NEW: Diagonal Drawing Type (v2.0 Phase 1)
type DiagonalDrawing
// Wave lines (0→1, 1→2, 2→3, 3→4, 4→5)
line l01
line l12
line l23
line l34
line l45
// Trendlines (converging wedge)
line trendLine1
line trendLine2
// Wave labels (0, 1, 2, 3, 4, 5)
label b0
label b1
label b2
label b3
label b4
label b5
// Type label ("Leading" or "Ending")
label typeLabel
// Metadata
bool active
int patternIndex
string diagonalType // "LEADING" or "ENDING"
// Global drawing pools
var array<WaveDrawing> impulseDrawings = array.new<WaveDrawing>()
var array<CorrectionDrawing> correctionDrawings = array.new<CorrectionDrawing>()
var array<DiagonalDrawing> diagonalDrawings = array.new<DiagonalDrawing>()
var array<CorrectionDrawing> flatDrawings = array.new<CorrectionDrawing>() // NEW: Flat corrections (reuse CorrectionDrawing)
var int maxActiveWaves = 5 // Max simultaneous wave displays
// ===== MODULE: HELPERS =====
// 4) HELPERS
// ───────────────────────────────────────────────────────────────────────────
// Pivot add + trim
f_add_pivot(float price, int idx, int dir) =>
array.push(pivPrice, price)
array.push(pivIndex, idx)
array.push(pivDir, dir)
array.push(pivTime, time)
while array.size(pivPrice) > pivotsToKeep
array.shift(pivPrice)
array.shift(pivIndex)
array.shift(pivDir)
array.shift(pivTime)
// Compute swing metrics on new pivot
f_calc_swing_metrics() =>
int size = array.size(pivPrice)
if size >= 2
float p1 = array.get(pivPrice, size - 2)
float p2 = array.get(pivPrice, size - 1)
int i1 = array.get(pivIndex, size - 2)
int i2 = array.get(pivIndex, size - 1)
float amp = math.abs(p2 - p1)
int bars = i2 - i1
array.push(swingAmp, amp)
array.push(swingBars, bars)
float retrace = na
float ext = na
if size >= 3
float p0 = array.get(pivPrice, size - 3)
float prevSwing = math.abs(p1 - p0)
retrace := prevSwing > 0 ? (amp / prevSwing) : na
float prevAmp = array.size(swingAmp) >= 1 ? array.get(swingAmp, array.size(swingAmp) - 1) : na
ext := na(prevAmp) or prevAmp == 0.0 ? na : amp / prevAmp
array.push(swingRetrace, retrace)
array.push(swingExt, ext)
while array.size(swingAmp) > pivotsToKeep - 1
array.shift(swingAmp)
array.shift(swingBars)
array.shift(swingRetrace)
array.shift(swingExt)
// Draw ZigZag line last two pivots
f_draw_zigzag_line() =>
if showZigZag and array.size(pivPrice) >= 2
int size = array.size(pivPrice)
float p1 = array.get(pivPrice, size - 2)
float p2 = array.get(pivPrice, size - 1)
int i1 = array.get(pivIndex, size - 2)
int i2 = array.get(pivIndex, size - 1)
int d2 = array.get(pivDir, size - 1)
color lineColor = d2 > 0 ? colorUp : colorDown
ln = line.new(i1, p1, i2, p2, xloc.bar_index, extend.none, lineColor, line.style_solid, 2)
array.push(zigzagLines, ln)
while array.size(zigzagLines) > 200
line.delete(array.shift(zigzagLines))
// Safe getters with live data support
// IMPORTANT: These check if we're requesting the LATEST pivot (array size - 1)
// If so, and there's an unconfirmed extreme being tracked, use that live value
// This ensures visualizations track the moving pivot correctly during zoom/pan
f_get_pivot_price(int idx) =>
if idx >= 0 and idx < array.size(pivPrice)
// Check if this is the latest pivot AND we have a newer unconfirmed extreme
isLatest = idx == array.size(pivPrice) - 1
if isLatest and not na(lastExtreme) and not na(lastExtremeIdx)
// Use live tracking value for latest pivot
lastExtreme
else
array.get(pivPrice, idx)
else
na
f_get_pivot_dir(int idx) =>
if idx >= 0 and idx < array.size(pivDir)
isLatest = idx == array.size(pivDir) - 1
if isLatest and currentDir != 0
currentDir
else
array.get(pivDir, idx)
else
0
f_get_pivot_index(int idx) =>
if idx >= 0 and idx < array.size(pivIndex)
isLatest = idx == array.size(pivIndex) - 1
if isLatest and not na(lastExtremeIdx)
lastExtremeIdx
else
array.get(pivIndex, idx)
else
na
// Alternation check (H-L-H-L ...)
f_check_alternation(int startIdx, int count) =>
bool valid = true
if array.size(pivDir) >= startIdx + count
for i = startIdx to startIdx + count - 2
int d1 = array.get(pivDir, i)
int d2 = array.get(pivDir, i + 1)
if d1 * d2 > 0
valid := false
break
else
valid := false
valid
// Overlap 1/4 forbidden for impulse
f_check_no_overlap(int startIdx) =>
bool noOverlap = true
if array.size(pivPrice) >= startIdx + 6
float p1 = array.get(pivPrice, startIdx + 1)
float p4 = array.get(pivPrice, startIdx + 4)
int dir1 = array.get(pivDir, startIdx + 1)
if dir1 > 0
if p4 < p1
noOverlap := false
else
if p4 > p1
noOverlap := false
else
noOverlap := false
noOverlap
// Fib helpers
f_in_fib_range(float actual, float target, float tol) =>
float lower = target * (1.0 - tol)
float upper = target * (1.0 + tol)
actual >= lower and actual <= upper
// Time ratio check (bars duration)
f_check_time_ratios(int idx0, int idx1, int idx2, int idx3, int idx4, int idx5) =>
int time1 = idx1 - idx0
int time3 = idx3 - idx2
int time5 = idx5 - idx4
bool time3longest = time3 >= time1 and time3 >= time5
float score = time3longest and time3 >= time1 * 1.2 ? 100.0 : (time3longest ? 80.0 : (time3 >= time1 * 0.8 ? 60.0 : 40.0))
[score, time3longest]
// Momentum at bar
f_get_momentum_at_bar(int barIdx) =>
int barsAgo = bar_index - barIdx
float momentum = na
if barsAgo >= 0
if momentumType == "RSI"
momentum := rsiSeries[barsAgo]
else if momentumType == "MACD"
momentum := macdHist[barsAgo]
momentum
// Wave-3 momentum confirmation
f_check_wave3_momentum(int idx1, int idx3, string direction) =>
float mom1 = f_get_momentum_at_bar(idx1)
float mom3 = f_get_momentum_at_bar(idx3)
float score = 70.0
if not na(mom1) and not na(mom3)
if direction == "UP"
if momentumType == "RSI"
score := (mom3 > mom1 and mom3 > 60) ? 100.0 : ((mom3 > mom1) ? 80.0 : 50.0)
else if momentumType == "MACD"
score := (mom3 > mom1 and mom3 > 0) ? 100.0 : ((mom3 > mom1) ? 80.0 : 50.0)
else
if momentumType == "RSI"
score := (mom3 < mom1 and mom3 < 40) ? 100.0 : ((mom3 < mom1) ? 80.0 : 50.0)
else if momentumType == "MACD"
score := (mom3 < mom1 and mom3 < 0) ? 100.0 : ((mom3 < mom1) ? 80.0 : 50.0)
[score, mom1, mom3]
// Volume confirmation (simplified)
f_check_volume_confirmation(int idx0, int idx1, int idx2, int idx3) =>
float score = 70.0
int bars1 = idx1 - idx0
int bars3 = idx3 - idx2
if bars1 > 0 and bars3 > 0
int barsAgo1 = bar_index - idx1
int barsAgo3 = bar_index - idx3
if barsAgo1 >= 0 and barsAgo3 >= 0
float vol1 = nz(volume[barsAgo1], 0)
float vol3 = nz(volume[barsAgo3], 0)
score := (vol1 > 0 and vol3 > vol1 * 1.2) ? 100.0 : ((vol3 > vol1) ? 80.0 : ((vol3 > vol1 * 0.8) ? 60.0 : 40.0))
score
// ═════════════════════════════════════════════════════════════════════════════
// ENHANCED PROJECTION SYSTEM (Priority 3)
// ═════════════════════════════════════════════════════════════════════════════
// Multi-scenario wave projections with IF-THEN logic based on wave relationships
// Calculate wave projection targets (Min/Ideal/Max)
// Enhanced with multiple scenario detection for more accurate projections
f_calculate_wave_targets(string waveType, bool isUp, float p0, float p1, float p2, float p3, float p4) =>
float minTarget = na
float idealTarget = na
float maxTarget = na
if waveType == "WAVE_3"
// ─────────────────────────────────────────────────────────────────────
// WAVE 3 PROJECTION SCENARIOS
// ─────────────────────────────────────────────────────────────────────
// Wave 3 is typically the strongest wave:
// - Minimum: 1.0 x Wave 1 (never shorter than Wave 1)
// - Typical: 1.618 x Wave 1 (golden ratio)
// - Extended: 2.618 x Wave 1 (strong trending markets)
// ─────────────────────────────────────────────────────────────────────
float wave1 = math.abs(p1 - p0)
float wave2 = not na(p2) ? math.abs(p2 - p1) : na
// Scenario 1: STANDARD IMPULSE (Wave 2 retraces 38.2%-61.8% of Wave 1)
if not na(wave2) and wave2 / wave1 >= 0.382 and wave2 / wave1 <= 0.618
minTarget := isUp ? p2 + (wave1 * 1.272) : p2 - (wave1 * 1.272)
idealTarget := isUp ? p2 + (wave1 * 1.618) : p2 - (wave1 * 1.618)
maxTarget := isUp ? p2 + (wave1 * 2.618) : p2 - (wave1 * 2.618)
// Scenario 2: SHALLOW CORRECTION (Wave 2 < 38.2% → Strong trend → Extended Wave 3)
else if not na(wave2) and wave2 / wave1 < 0.382
minTarget := isUp ? p2 + (wave1 * 1.618) : p2 - (wave1 * 1.618)
idealTarget := isUp ? p2 + (wave1 * 2.618) : p2 - (wave1 * 2.618)
maxTarget := isUp ? p2 + (wave1 * 3.618) : p2 - (wave1 * 3.618)
// Scenario 3: DEEP CORRECTION (Wave 2 > 61.8% → Weak trend → Shorter Wave 3)
else if not na(wave2) and wave2 / wave1 > 0.618
minTarget := isUp ? p2 + (wave1 * 1.0) : p2 - (wave1 * 1.0)
idealTarget := isUp ? p2 + (wave1 * 1.272) : p2 - (wave1 * 1.272)
maxTarget := isUp ? p2 + (wave1 * 1.618) : p2 - (wave1 * 1.618)
// Scenario 4: FALLBACK (no Wave 2 data)
else
minTarget := isUp ? p2 + (wave1 * 1.0) : p2 - (wave1 * 1.0)
idealTarget := isUp ? p2 + (wave1 * 1.618) : p2 - (wave1 * 1.618)
maxTarget := isUp ? p2 + (wave1 * 2.618) : p2 - (wave1 * 2.618)
else if waveType == "WAVE_5"
// ─────────────────────────────────────────────────────────────────────
// WAVE 5 PROJECTION SCENARIOS
// ─────────────────────────────────────────────────────────────────────
// Wave 5 projection depends on Wave 1 and Wave 3 relationship:
// IF Wave 3 = Extended (> 1.618 x Wave 1)
// THEN Wave 5 = Short (0.618 x Wave 1)
// IF Wave 3 = Normal (1.0-1.618 x Wave 1)
// THEN Wave 5 = Normal (0.618-1.0 x Wave 1)
// IF Wave 3 = Short (< 1.0 x Wave 1) [INVALID but handle gracefully]
// THEN Wave 5 = Extended (1.0-1.618 x Wave 1)
// ─────────────────────────────────────────────────────────────────────
float wave1 = math.abs(p1 - p0)
float wave3 = not na(p3) ? math.abs(p3 - p2) : na
float wave4 = not na(p4) ? math.abs(p4 - p3) : na
// Calculate Wave 3 extension ratio
float wave3Ratio = not na(wave3) ? wave3 / wave1 : na
// Scenario 1: WAVE 3 EXTENDED (> 1.618 x Wave 1 → Wave 5 will be shorter)
if not na(wave3Ratio) and wave3Ratio > 1.618
minTarget := isUp ? p4 + (wave1 * 0.382) : p4 - (wave1 * 0.382)
idealTarget := isUp ? p4 + (wave1 * 0.618) : p4 - (wave1 * 0.618)
maxTarget := isUp ? p4 + (wave1 * 1.0) : p4 - (wave1 * 1.0)
// Scenario 2: WAVE 3 NORMAL (1.0-1.618 x Wave 1 → Wave 5 normal)
else if not na(wave3Ratio) and wave3Ratio >= 1.0 and wave3Ratio <= 1.618
minTarget := isUp ? p4 + (wave1 * 0.618) : p4 - (wave1 * 0.618)
idealTarget := isUp ? p4 + (wave1 * 1.0) : p4 - (wave1 * 1.0)
maxTarget := isUp ? p4 + (wave1 * 1.618) : p4 - (wave1 * 1.618)
// Scenario 3: WAVE 3 SHORT (< 1.0 x Wave 1 → Wave 5 might extend)
// Note: This violates Elliott Wave rules (Wave 3 can't be shortest)
// But we handle it gracefully for incomplete patterns
else if not na(wave3Ratio) and wave3Ratio < 1.0
minTarget := isUp ? p4 + (wave1 * 1.0) : p4 - (wave1 * 1.0)
idealTarget := isUp ? p4 + (wave1 * 1.618) : p4 - (wave1 * 1.618)
maxTarget := isUp ? p4 + (wave1 * 2.618) : p4 - (wave1 * 2.618)
// Scenario 4: ALTERNATIVE - Use 0.618 of Wave 1-3 total distance
else if not na(p3)
float totalDistance = math.abs(p3 - p0)
minTarget := isUp ? p4 + (totalDistance * 0.382) : p4 - (totalDistance * 0.382)
idealTarget := isUp ? p4 + (totalDistance * 0.618) : p4 - (totalDistance * 0.618)
maxTarget := isUp ? p4 + (totalDistance * 1.0) : p4 - (totalDistance * 1.0)
// Scenario 5: FALLBACK (minimal data)
else
minTarget := isUp ? p4 + (wave1 * 0.618) : p4 - (wave1 * 0.618)
idealTarget := isUp ? p4 + (wave1 * 1.0) : p4 - (wave1 * 1.0)
maxTarget := isUp ? p4 + (wave1 * 1.618) : p4 - (wave1 * 1.618)
else if waveType == "WAVE_C"
// ─────────────────────────────────────────────────────────────────────
// WAVE C PROJECTION (Corrections)
// ─────────────────────────────────────────────────────────────────────
float waveA = math.abs(p1 - p0)
float waveB = not na(p2) ? math.abs(p2 - p1) : na
// Scenario 1: ZIGZAG (Wave B sharp correction ~50%)
if not na(waveB) and waveB / waveA >= 0.382 and waveB / waveA <= 0.618
minTarget := isUp ? p2 + (waveA * 1.0) : p2 - (waveA * 1.0)
idealTarget := isUp ? p2 + (waveA * 1.272) : p2 - (waveA * 1.272)
maxTarget := isUp ? p2 + (waveA * 1.618) : p2 - (waveA * 1.618)
// Scenario 2: EXTENDED C (Wave B shallow)
else if not na(waveB) and waveB / waveA < 0.382
minTarget := isUp ? p2 + (waveA * 1.272) : p2 - (waveA * 1.272)
idealTarget := isUp ? p2 + (waveA * 1.618) : p2 - (waveA * 1.618)
maxTarget := isUp ? p2 + (waveA * 2.618) : p2 - (waveA * 2.618)
// Scenario 3: FALLBACK
else
minTarget := isUp ? p2 + (waveA * 1.0) : p2 - (waveA * 1.0)
idealTarget := isUp ? p2 + (waveA * 1.272) : p2 - (waveA * 1.272)
maxTarget := isUp ? p2 + (waveA * 1.618) : p2 - (waveA * 1.618)
[minTarget, idealTarget, maxTarget]
// ─────────────────────────────────────────────────────────────────────────────
// PROJECTION SCENARIO DETECTOR
// ─────────────────────────────────────────────────────────────────────────────
// Returns human-readable scenario description for info display
f_get_projection_scenario(string waveType, float p0, float p1, float p2, float p3, float p4) =>
string scenario = "Unknown"
if waveType == "WAVE_3"
float wave1 = math.abs(p1 - p0)
float wave2 = not na(p2) ? math.abs(p2 - p1) : na
if not na(wave2)
float retrace = wave2 / wave1
if retrace < 0.382
scenario := "Shallow W2 → Extended W3"
else if retrace >= 0.382 and retrace <= 0.618
scenario := "Standard Impulse"
else
scenario := "Deep W2 → Short W3"
else
scenario := "Insufficient Data"
else if waveType == "WAVE_5"
float wave1 = math.abs(p1 - p0)
float wave3 = not na(p3) ? math.abs(p3 - p2) : na
if not na(wave3)
float ratio = wave3 / wave1
if ratio > 1.618
scenario := "W3 Extended → Short W5"
else if ratio >= 1.0 and ratio <= 1.618
scenario := "W3 Normal → Normal W5"
else if ratio < 1.0
scenario := "W3 Short → Extended W5"
else
scenario := "Alternative: 0.618 Total"
else
scenario := "Insufficient Data"
scenario
// ===== MODULE: INPUTS =====
// 1) INPUTS
// ───────────────────────────────────────────────────────────────────────────
// ZigZag Settings
string mode = input.string("ATR", "Detection Mode", options=["ATR", "Percent"], group="🔍 ZigZag Settings")
int atrLen = input.int(14, "ATR Length", minval=1, group="🔍 ZigZag Settings")
float atrMult = input.float(2.0, "ATR Multiplier", minval=0.1, step=0.1, group="🔍 ZigZag Settings")
float percentThr = input.float(2.0, "Percent Threshold", minval=0.1, step=0.1, group="🔍 ZigZag Settings", tooltip="Used only in Percent mode")
int minSwingBars = input.int(10, "Min Swing Bars", minval=1, group="🔍 ZigZag Settings", tooltip="Minimum bars for swing confirmation")
int smoothLen = input.int(3, "Smoothing Length", minval=0, group="🔍 ZigZag Settings", tooltip="0 = no smoothing")
int pivotsToKeep = input.int(21, "Max Pivots Stored", minval=9, maxval=256, group="🔍 ZigZag Settings")
// Pattern Detection
bool enablePatterns = input.bool(true, "Enable Pattern Detection", group="🎯 Pattern Detection")
int minPivotsFor5 = input.int(6, "Min Pivots for 5-Wave", minval=6, maxval=10, group="🎯 Pattern Detection")
int minPivotsFor3 = input.int(4, "Min Pivots for 3-Wave", minval=4, maxval=6, group="🎯 Pattern Detection")
int windowsBack5 = input.int(6, "Scan back 5-wave windows", minval=1, maxval=50, group="🎯 Pattern Detection")
int windowsBack3 = input.int(6, "Scan back 3-wave windows", minval=1, maxval=50, group="🎯 Pattern Detection")
bool enableDiagonals = input.bool(true, "Enable Diagonal Detection", group="🎯 Pattern Detection", tooltip="Leading & Ending Diagonals")
bool enableFlats = input.bool(true, "Enable Flat Corrections", group="🎯 Pattern Detection", tooltip="Regular, Expanded & Running Flats")
bool showPatternLabels = input.bool(true, "Show Pattern Labels", group="🎯 Pattern Detection")
bool showPatternInfo = input.bool(true, "Show Pattern Info", group="🎯 Pattern Detection")
// Rule Engine
float fibTolerance = input.float(0.10, "Fibonacci Tolerance", minval=0.0, maxval=0.5, step=0.01, group="⚖️ Rule Engine", tooltip="±tolerance for Fib ratios (e.g., 0.10 = ±10%)")
bool strictWave3 = input.bool(true, "Strict Wave 3 Rule", group="⚖️ Rule Engine", tooltip="Wave 3 must never be the shortest")
bool checkTimeRatios = input.bool(true, "Check Time Ratios", group="⚖️ Rule Engine", tooltip="Validate time proportions (Wave 3 duration)")
bool enableFibChecks = input.bool(true, "Enable Fibonacci Checks", group="⚖️ Rule Engine")
float minScoreThreshold = input.float(60.0, "Min Score for Display", minval=0, maxval=100, step=5, group="⚖️ Rule Engine", tooltip="Only show patterns above this score")
// Momentum & Volume
bool enableMomentum = input.bool(true, "Enable Momentum Checks", group="📈 Momentum & Volume")
string momentumType = input.string("RSI", "Momentum Indicator", options=["RSI", "MACD", "None"], group="📈 Momentum & Volume")
int momentumLen = input.int(14, "Momentum Length", minval=5, maxval=50, group="📈 Momentum & Volume")
bool enableVolume = input.bool(true, "Enable Volume Checks", group="📈 Momentum & Volume", tooltip="Check if Wave 3 has higher volume than Wave 1")
int showTopPatterns = input.int(2, "Show Top N Patterns", minval=1, maxval=3, group="📈 Momentum & Volume", tooltip="Display best + alternatives")
// Visual Settings
bool showPivots = input.bool(true, "Show Pivot Labels", group="📊 Visualization")
bool showZigZag = input.bool(true, "Draw ZigZag Lines", group="📊 Visualization")
bool showMetrics = input.bool(true, "Show Swing Metrics", group="📊 Visualization")
bool showConfBox = input.bool(true, "Show Confidence Box", group="📊 Visualization")
color colorUp = input.color(color.new(#2979FF, 0), "Up Swing Color", group="📊 Visualization")
color colorDown = input.color(color.new(#FF6B00, 0), "Down Swing Color", group="📊 Visualization")
color colorCorrection = input.color(color.new(#FF9800, 0), "Correction Color", group="📊 Visualization")
bool showDebug = input.bool(false, "Debug Mode", group="📊 Visualization", tooltip="Shows additional info")
// Wave Projection
bool showProjection = input.bool(true, "Show Wave Projection", group="🎯 Wave Targets")
bool showTargets = input.bool(true, "Show Target Levels", group="🎯 Wave Targets")
string infoPosition = input.string("Middle Right", "Info Table Position", ["Top Left", "Top Right", "Middle Right", "Bottom Left", "Bottom Right"], group="📊 Visualization")
// ===== MODULE: VISUALIZATION =====
// DEBUG: Log live pivot tracking status to Pine Logs
if showDebug and barstate.islast
int pivCount = array.size(pivPrice)
log.info("=== WavePilot Debug Info ===")
log.info("Total Pivots: {0}", pivCount)
if pivCount > 0
int lastIdx = pivCount - 1
float staticPrice = array.get(pivPrice, lastIdx)
float livePrice = lastExtreme
int staticBar = array.get(pivIndex, lastIdx)
int liveBar = lastExtremeIdx
log.info("Last Static Pivot: {0} @ bar {1}", staticPrice, staticBar)
log.info("Live Tracking Pivot: {0} @ bar {1}", livePrice, liveBar)
bool usingLive = (staticBar != liveBar or staticPrice != livePrice)
if usingLive
log.info("✓ Live pivot tracking is ACTIVE (values differ)")
else
log.warning("✗ Live pivot tracking NOT active (values same - pivot already confirmed)")
// Log pattern detection info
if array.size(detectedPatterns) > 0
PatternCandidate p = array.get(detectedPatterns, 0)
log.info("Best Pattern: {0} {1}, Confidence: {2}%", p.patternType, p.direction, p.confidence)
// Log pivot prices being used for visualization
float p0 = f_get_pivot_price(p.startIdx)
float p5 = f_get_pivot_price(p.startIdx + 5)
int x0 = f_get_pivot_index(p.startIdx)
int x5 = f_get_pivot_index(p.startIdx + 5)
if not na(p0) and not na(p5)
log.info("Wave Start (P0): {0} @ bar {1}", p0, x0)
log.info("Wave End (P5): {0} @ bar {1}", p5, x5)
else
log.warning("No patterns detected")
// Main visualization update (runs EVERY bar, not just barstate.islast)
if showPatternLabels and array.size(detectedPatterns) > 0
// Sort patterns by confidence
array<PatternCandidate> sorted = array.copy(detectedPatterns)
int n = array.size(sorted)
if n > 1
for i = 0 to n - 2
for j = 0 to n - i - 2
PatternCandidate a = array.get(sorted, j)
PatternCandidate b = array.get(sorted, j + 1)
if a.confidence < b.confidence
array.set(sorted, j, b)
array.set(sorted, j + 1, a)
// Update visible patterns
int toShow = int(math.min(showTopPatterns, n))
int impulseSlot = 0
int correctionSlot = 0
int diagonalSlot = 0 // v2.0 Phase 1
int flatSlot = 0 // v2.0 Phase 2 - Priority 2
for rank = 0 to toShow - 1
PatternCandidate p = array.get(sorted, rank)
// Show pattern if: (valid AND good score) OR (high confidence regardless of validity)
bool shouldShow = (p.isValid and p.confidence >= minScoreThreshold) or (p.confidence >= 75.0)
if shouldShow
if p.patternType == "IMPULSE" and p.pivotCount == 6 and impulseSlot < maxActiveWaves
// Get drawing slot
wd = array.get(impulseDrawings, impulseSlot)
wd.active := true
wd.patternIndex := p.startIdx
// Update visualization
f_update_impulse_drawing(wd, p, rank)
impulseSlot += 1
else if p.patternType == "CORRECTION" and p.pivotCount == 4 and correctionSlot < maxActiveWaves
// DEBUG: Log correction pattern processing
if showDebug and barstate.islast
log.info("=== PROCESSING CORRECTION PATTERN ===")
log.info("Rank: {0}, Confidence: {1}%, Valid: {2}", rank, p.confidence, p.isValid)
log.info("CorrectionSlot: {0}, MaxSlots: {1}", correctionSlot, maxActiveWaves)
// Get drawing slot
cd = array.get(correctionDrawings, correctionSlot)
cd.active := true
cd.patternIndex := p.startIdx
// Update visualization
f_update_correction_drawing(cd, p, rank)
correctionSlot += 1
if showDebug and barstate.islast
log.info("✓ CORRECTION visualization updated in slot {0}", correctionSlot - 1)
// v2.0 Phase 1: Diagonal patterns
else if (p.patternType == "LEADING_DIAGONAL" or p.patternType == "ENDING_DIAGONAL") and p.pivotCount == 6 and diagonalSlot < maxActiveWaves
// Get drawing slot
dd = array.get(diagonalDrawings, diagonalSlot)
dd.active := true
dd.patternIndex := p.startIdx
dd.diagonalType := p.patternType == "LEADING_DIAGONAL" ? "LEADING" : "ENDING"
// Update visualization
f_update_diagonal_drawing(dd, p, rank)
diagonalSlot += 1
// v2.0 Phase 2: Flat corrections (Priority 2)
else if (p.patternType == "REGULAR_FLAT" or p.patternType == "EXPANDED_FLAT" or p.patternType == "RUNNING_FLAT") and p.pivotCount == 4 and flatSlot < maxActiveWaves
// Get drawing slot
fd = array.get(flatDrawings, flatSlot)
fd.active := true
fd.patternIndex := p.startIdx
// Update visualization
f_update_flat_drawing(fd, p, rank)
flatSlot += 1
// Hide unused slots
for i = impulseSlot to maxActiveWaves - 1
wd = array.get(impulseDrawings, i)
if wd.active
f_hide_impulse_drawing(wd)
wd.active := false
for i = correctionSlot to maxActiveWaves - 1
cd = array.get(correctionDrawings, i)
if cd.active
f_hide_correction_drawing(cd)
cd.active := false
// v2.0 Phase 1: Hide unused diagonal slots
for i = diagonalSlot to maxActiveWaves - 1
dd = array.get(diagonalDrawings, i)
if dd.active
f_hide_diagonal_drawing(dd)
dd.active := false
// v2.0 Phase 2: Hide unused flat slots (Priority 2)
for i = flatSlot to maxActiveWaves - 1
fd = array.get(flatDrawings, i)
if fd.active
f_hide_flat_drawing(fd)
fd.active := false
// Hide all drawings if no patterns
if not showPatternLabels or array.size(detectedPatterns) == 0
for i = 0 to maxActiveWaves - 1
wd = array.get(impulseDrawings, i)
if wd.active
f_hide_impulse_drawing(wd)
wd.active := false
cd = array.get(correctionDrawings, i)
if cd.active
f_hide_correction_drawing(cd)
cd.active := false
// v2.0 Phase 1: Hide diagonals
dd = array.get(diagonalDrawings, i)
if dd.active
f_hide_diagonal_drawing(dd)
dd.active := false
// v2.0 Phase 2: Hide flats (Priority 2)
fd = array.get(flatDrawings, i)
if fd.active
f_hide_flat_drawing(fd)
fd.active := false
// ===== MODULE: DETECTION_FLAT =====
// Scan for FLAT CORRECTION patterns (v2.0 Phase 2 - Priority 2)
f_detect_flat_patterns() =>
int pc = array.size(pivPrice)
if enablePatterns and enableFlats and pc >= minPivotsFor3
int startLatest = pc - minPivotsFor3
int startEarliest = math.max(0, startLatest - (windowsBack3 - 1))
int span = startLatest - startEarliest
if span >= 0
for off = 0 to span
int startIdx = startLatest - off
float p0 = f_get_pivot_price(startIdx)
float p1 = f_get_pivot_price(startIdx + 1)
float p2 = f_get_pivot_price(startIdx + 2)
float p3 = f_get_pivot_price(startIdx + 3)
if na(p0) or na(p3)
continue
// Check for Flat Correction
[isFlat, flatType, flatConf, bRetrace, cVsA] = f_check_flat_correction(p0, p1, p2, p3)
if isFlat
// Create pattern candidate with flat type
string patternName = flatType == "REGULAR" ? "REGULAR_FLAT" : (flatType == "EXPANDED" ? "EXPANDED_FLAT" : "RUNNING_FLAT")
c = PatternCandidate.new(patternName, startIdx, startIdx + 3, 4, true, "UNKNOWN", array.new_string(), flatConf, 100.0, 100.0, 0.0, 0.0, 70.0, 70.0)
// Check alternation
bool alternates = f_check_alternation(startIdx, 4)
if not alternates
array.push(c.violations, "No alternation")
// Add B retrace info
if bRetrace < 0.90 or bRetrace > 1.38
array.push(c.violations, str.format("B {0,number,#.##}% of A", bRetrace*100))
// Add C extension info
if flatType == "EXPANDED" and (cVsA < 1.10 or cVsA > 1.618)
array.push(c.violations, str.format("C {0,number,#.##}% of A", cVsA*100))
else if flatType == "RUNNING" and cVsA >= 1.10
array.push(c.violations, str.format("C {0,number,#.##}% (too long)", cVsA*100))
// Determine direction
int dir1 = f_get_pivot_dir(startIdx + 1)
c.direction := dir1 > 0 ? "UP" : "DOWN"
array.push(detectedPatterns, c)
// ===== MODULE: INFO_TABLE =====
// 8) PATTERN INFO TABLE (Top-1)
// ───────────────────────────────────────────────────────────────────────────
if showPatternInfo and barstate.islast and array.size(detectedPatterns) > 0
array<PatternCandidate> sorted = array.copy(detectedPatterns)
int n = array.size(sorted)
if n > 1
for i = 0 to n - 2
for j = 0 to n - i - 2
PatternCandidate a = array.get(sorted, j)
PatternCandidate b = array.get(sorted, j + 1)
if a.confidence < b.confidence
array.set(sorted, j, b)
array.set(sorted, j + 1, a)
PatternCandidate bp = array.get(sorted, 0)
int rows = 12 // Increased for more info
// Dynamic position based on user input
var tablePos = switch infoPosition
"Top Left" => position.top_left
"Top Right" => position.top_right
"Middle Right" => position.middle_right
"Bottom Left" => position.bottom_left
"Bottom Right" => position.bottom_right
=> position.middle_right
// Get wave start/end info
float pStart = f_get_pivot_price(bp.startIdx)
float pEnd = f_get_pivot_price(bp.startIdx + bp.pivotCount - 1)
int xStart = f_get_pivot_index(bp.startIdx)
int xEnd = f_get_pivot_index(bp.startIdx + bp.pivotCount - 1)
string waveRange = str.tostring(pStart, format.mintick) + " → " + str.tostring(pEnd, format.mintick)
string barRange = str.tostring(xEnd - xStart) + " bars"
// Visual marker at pattern location (highlight box)
color patternColor = bp.patternType == "IMPULSE" ? colorUp : (bp.patternType == "CORRECTION" ? colorCorrection : color.purple)
if not na(pStart) and not na(pEnd) and not na(xStart) and not na(xEnd)
// Draw a box around the pattern for visual connection
box.new(xStart, pStart, xEnd, pEnd, border_color=color.new(patternColor, 30), bgcolor=color.new(patternColor, 95), border_width=2, border_style=line.style_dashed)
// Recreate table on each bar - LARGER TEXT
table pt = table.new(tablePos, 2, rows, color.new(color.black, 40), color.new(color.blue,30), 3, color.new(color.blue,30), 3)
table.cell(pt, 0, 0, "🌊 Pattern Info", bgcolor=color.new(color.blue, 20), text_color=color.white, text_size=size.large)
table.cell(pt, 1, 0, "", bgcolor=color.new(color.blue, 20), text_color=color.white, text_size=size.large)
table.cell(pt, 0, 1, "Type", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 50))
table.cell(pt, 1, 1, bp.patternType, text_size=size.normal, text_color=color.yellow, bgcolor=color.new(color.black, 50))
table.cell(pt, 0, 2, "Direction", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 50))
string dirIcon = bp.direction == "UP" ? "📈 " : "📉 "
table.cell(pt, 1, 2, dirIcon + bp.direction, text_size=size.normal, text_color=color.aqua, bgcolor=color.new(color.black, 50))
table.cell(pt, 0, 3, "Confidence", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 50))
color confCol = bp.confidence >= 80 ? color.lime : (bp.confidence >= 60 ? color.yellow : color.orange)
table.cell(pt, 1, 3, str.format("{0,number,#.0}%", bp.confidence), text_size=size.normal, text_color=confCol, bgcolor=color.new(color.black, 50))
table.cell(pt, 0, 4, "Valid", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 50))
table.cell(pt, 1, 4, bp.isValid ? "✓ Yes" : "✗ No", text_size=size.normal, text_color = bp.isValid ? color.lime : color.red, bgcolor=color.new(color.black, 50))
// Wave Details
table.cell(pt, 0, 5, "Range", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 50))
table.cell(pt, 1, 5, waveRange, text_size=size.small, text_color=color.white, bgcolor=color.new(color.black, 50))
table.cell(pt, 0, 6, "Duration", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 50))
table.cell(pt, 1, 6, barRange, text_size=size.normal, text_color=color.white, bgcolor=color.new(color.black, 50))
// Breakdown scores - CONSISTENT SIZE.NORMAL
table.cell(pt, 0, 7, "Structure", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 60))
table.cell(pt, 1, 7, str.format("{0,number,#}", bp.structureScore), text_size=size.normal, text_color=color.white, bgcolor=color.new(color.black, 60))
table.cell(pt, 0, 8, "Retracement",text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 60))
table.cell(pt, 1, 8, str.format("{0,number,#}", bp.retracementScore), text_size=size.normal, text_color=color.white, bgcolor=color.new(color.black, 60))
table.cell(pt, 0, 9, "Extension", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 60))
table.cell(pt, 1, 9, str.format("{0,number,#}", bp.extensionScore), text_size=size.normal, text_color=color.white, bgcolor=color.new(color.black, 60))
table.cell(pt, 0, 10, "Momentum", text_size=size.normal, text_color=color.silver, bgcolor=color.new(color.black, 60))
table.cell(pt, 1, 10, str.format("{0,number,#}", bp.momentumScore), text_size=size.normal, text_color=color.white, bgcolor=color.new(color.black, 60))
// Violations
string viol = ""
int vmax = int(math.min(array.size(bp.violations), 2))
if vmax > 0
for i = 0 to vmax - 1
viol += array.get(bp.violations, i)
if i < vmax - 1
viol += ", "
table.cell(pt, 0, 11, "Issues", text_size=size.normal, text_color=color.orange, bgcolor=color.new(color.black, 60))
table.cell(pt, 1, 11, viol == "" ? "None" : viol, text_size=size.normal, text_color=color.white, bgcolor=color.new(color.black, 60))
// 9) INFO (Bottom-Right)
// ───────────────────────────────────────────────────────────────────────────
if barstate.islast and showConfBox
// Recreate table on each bar to allow scaling - better contrast
table it = table.new(position.bottom_right, 2, 4, color.new(color.black,50), color.new(color.gray,40), 2, color.new(color.gray,40), 2)
table.cell(it, 0, 0, "WavePilot", bgcolor=color.new(color.blue,30), text_color=color.white, text_size=size.normal, tooltip="Sprint 1+2+3+4+5: Full System")
table.cell(it, 1, 0, "v1.4", bgcolor=color.new(color.blue,30), text_color=color.white, text_size=size.normal)
int pivotCount = array.size(pivPrice)
table.cell(it, 0, 1, "Pivots", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(it, 1, 1, str.tostring(pivotCount), text_size=size.small, text_color=color.yellow, bgcolor=color.new(color.black,50))
table.cell(it, 0, 2, "Mode", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(it, 1, 2, mode, text_size=size.small, text_color=color.aqua, bgcolor=color.new(color.black,50))
table.cell(it, 0, 3, "Patterns", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(it, 1, 3, str.tostring(array.size(detectedPatterns)), text_size=size.small, text_color=color.lime, bgcolor=color.new(color.black,50))
// ===== MODULE: UPDATE_FLAT =====
// Helper: Update single flat correction drawing (v2.0 Phase 2 - Priority 2)
f_update_flat_drawing(CorrectionDrawing cd, PatternCandidate p, int rank) =>
// Get all pivot data (A-B-C structure, 4 pivots total: 0-A-B-C)
p0 = f_get_pivot_price(p.startIdx) // Start
pA = f_get_pivot_price(p.startIdx + 1) // Wave A end
pB = f_get_pivot_price(p.startIdx + 2) // Wave B end
pC = f_get_pivot_price(p.startIdx + 3) // Wave C end
x0 = f_get_pivot_index(p.startIdx)
xA = f_get_pivot_index(p.startIdx + 1)
xB = f_get_pivot_index(p.startIdx + 2)
xC = f_get_pivot_index(p.startIdx + 3)
d0 = f_get_pivot_dir(p.startIdx)
dA = f_get_pivot_dir(p.startIdx + 1)
dB = f_get_pivot_dir(p.startIdx + 2)
dC = f_get_pivot_dir(p.startIdx + 3)
// Colors and styling (orange for all corrections, including flats)
alpha = rank == 0 ? 30 : 70
lineColor = colorCorrection // Orange
labelColor = color.white
bgColor = color(na)
// Update correction lines (0→A, A→B, B→C)
if not na(pA) and not na(xA)
cd.lAB.set_xy1(x0, p0)
cd.lAB.set_xy2(xA, pA)
cd.lAB.set_color(color.new(lineColor, alpha))
cd.lAB.set_width(2)
else
cd.lAB.set_color(na)
if not na(pB) and not na(xB)
cd.lBC.set_xy1(xA, pA)
cd.lBC.set_xy2(xB, pB)
cd.lBC.set_color(color.new(lineColor, alpha))
cd.lBC.set_width(2)
else
cd.lBC.set_color(na)
if not na(pC) and not na(xC)
cd.lCD.set_xy1(xB, pB)
cd.lCD.set_xy2(xC, pC)
cd.lCD.set_color(color.new(lineColor, alpha))
cd.lCD.set_width(2)
else
cd.lCD.set_color(na)
// Update labels with flat type indication
labelSize = rank == 0 ? size.normal : size.small
suffix = rank > 0 ? "*" : ""
// Determine flat type for labels
string flatTypeLabel = ""
if p.patternType == "REGULAR_FLAT"
flatTypeLabel := "R" // Regular
else if p.patternType == "EXPANDED_FLAT"
flatTypeLabel := "E" // Expanded
else if p.patternType == "RUNNING_FLAT"
flatTypeLabel := "Rn" // Running
if not na(p0) and not na(x0)
cd.bA.set_xy(x0, p0)
cd.bA.set_text("0" + suffix)
cd.bA.set_style(d0 > 0 ? label.style_label_down : label.style_label_up)
cd.bA.set_textcolor(labelColor)
cd.bA.set_color(bgColor)
cd.bA.set_size(labelSize)
else
cd.bA.set_textcolor(na)
if not na(pA) and not na(xA)
cd.bB.set_xy(xA, pA)
cd.bB.set_text("A" + suffix)
cd.bB.set_style(dA > 0 ? label.style_label_down : label.style_label_up)
cd.bB.set_textcolor(labelColor)
cd.bB.set_color(bgColor)
cd.bB.set_size(labelSize)
else
cd.bB.set_textcolor(na)
if not na(pC) and not na(xC)
cd.bC.set_xy(xB, pB)
cd.bC.set_text("B" + suffix)
cd.bC.set_style(dB > 0 ? label.style_label_down : label.style_label_up)
cd.bC.set_textcolor(labelColor)
cd.bC.set_color(bgColor)
cd.bC.set_size(labelSize)
else
cd.bC.set_textcolor(na)
// Wave C label includes flat type indicator
string cText = "C" + suffix
if not na(pC) and not na(xC)
cd.bD.set_xy(xC, pC)
cd.bD.set_text(cText + " [" + flatTypeLabel + "]")
cd.bD.set_style(dC > 0 ? label.style_label_down : label.style_label_up)
cd.bD.set_textcolor(labelColor)
cd.bD.set_color(bgColor)
cd.bD.set_size(labelSize)
else
cd.bD.set_textcolor(na)
f_hide_flat_drawing(CorrectionDrawing cd) =>
cd.lAB.set_color(na)
cd.lBC.set_color(na)
cd.lCD.set_color(na)
cd.bA.set_textcolor(na)
cd.bB.set_textcolor(na)
cd.bC.set_textcolor(na)
cd.bD.set_textcolor(na)
// ===== MODULE: DETECTION_IMPULSE =====
// 5) PATTERN DETECTION
// ───────────────────────────────────────────────────────────────────────────
// Scan sliding windows for 5-wave impulse
// NOTE: This function must be called on every bar for PineScript consistency
// (it uses series data via f_check_wave3_momentum)
f_detect_5_wave_patterns() =>
array.clear(detectedPatterns)
int pc = array.size(pivPrice)
// Process patterns only if enabled
if enablePatterns and pc >= minPivotsFor5
int startLatest = pc - minPivotsFor5
int startEarliest = math.max(0, startLatest - (windowsBack5 - 1))
// Only run loop if we have valid range
int span = startLatest - startEarliest
if span >= 0
// iterate with positive step using offset
for off = 0 to span
int startIdx = startLatest - off
float p0 = f_get_pivot_price(startIdx)
float p1 = f_get_pivot_price(startIdx + 1)
float p2 = f_get_pivot_price(startIdx + 2)
float p3 = f_get_pivot_price(startIdx + 3)
float p4 = f_get_pivot_price(startIdx + 4)
float p5 = f_get_pivot_price(startIdx + 5)
if na(p0) or na(p5)
continue
c = PatternCandidate.new("IMPULSE", startIdx, startIdx + 5, 6, false, "UNKNOWN", array.new_string(), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
bool alternates = f_check_alternation(startIdx, 6)
bool noOverlap = f_check_no_overlap(startIdx)
if not alternates
array.push(c.violations, "No alternation")
if not noOverlap
array.push(c.violations, "Wave 1/4 overlap")
c.structureScore := (alternates ? 50.0 : 0.0) + (noOverlap ? 50.0 : 0.0)
if enableFibChecks
[score2, retr2] = f_check_wave2_retracement(p0, p1, p2)
[score4, retr4] = f_check_wave4_retracement(p1, p2, p3, p4)
c.retracementScore := (score2 + score4) / 2.0
if retr2 < 0.30 or retr2 > 0.75
array.push(c.violations, str.format("W2 {0,number,#.##}%", retr2*100))
if retr4 < 0.15 or retr4 > 0.65
array.push(c.violations, str.format("W4 {0,number,#.##}%", retr4*100))
[score3ext, ratio3] = f_check_wave3_extension(p0, p1, p2, p3)
[score5ext, ratio5] = f_check_wave5_extension(p0, p1, p4, p5)
c.extensionScore := (score3ext + score5ext) / 2.0
[_, notShortest] = f_check_wave3_not_shortest(p0, p1, p2, p3, p4, p5)
if strictWave3 and not notShortest
array.push(c.violations, "Wave 3 shortest")
c.structureScore := c.structureScore * 0.5
c.overlapScore := noOverlap ? 100.0 : 0.0
if checkTimeRatios
int idx0 = f_get_pivot_index(startIdx)
int idx1 = f_get_pivot_index(startIdx + 1)
int idx2 = f_get_pivot_index(startIdx + 2)
int idx3 = f_get_pivot_index(startIdx + 3)
int idx4 = f_get_pivot_index(startIdx + 4)
int idx5 = f_get_pivot_index(startIdx + 5)
if not na(idx0) and not na(idx5)
[tScore, tOk] = f_check_time_ratios(idx0, idx1, idx2, idx3, idx4, idx5)
c.timeScore := tScore
if not tOk
array.push(c.violations, "Wave 3 not longest time")
else
c.timeScore := 70.0
int dir1 = f_get_pivot_dir(startIdx + 1)
c.direction := dir1 > 0 ? "UP" : "DOWN"
// Check momentum
if enableMomentum and momentumType != "None"
int idx1 = f_get_pivot_index(startIdx + 1)
int idx3 = f_get_pivot_index(startIdx + 3)
if not na(idx1) and not na(idx3)
[momScore, _, _] = f_check_wave3_momentum(idx1, idx3, c.direction)
c.momentumScore := momScore
if enableVolume
int idx0 = f_get_pivot_index(startIdx)
int idx2 = f_get_pivot_index(startIdx + 2)
if not na(idx0) and not na(idx2)
float volScore = f_check_volume_confirmation(idx0, idx1, idx2, idx3)
c.momentumScore := (c.momentumScore * 0.7) + (volScore * 0.3)
else
c.momentumScore := 70.0
[w3Score, w3Valid] = f_check_wave3_not_shortest(p0, p1, p2, p3, p4, p5)
c.isValid := alternates and noOverlap and (not strictWave3 or (strictWave3 and w3Valid))
// Final confidence (Sprint 4 weights)
c.confidence := (c.structureScore * 0.35) + (c.retracementScore * 0.25) + (c.overlapScore * 0.15) + (c.extensionScore * 0.08) + (c.timeScore * 0.07) + (c.momentumScore * 0.10)
array.push(detectedPatterns, c)
// ===== MODULE: CHECKS_CORRECTION =====
// Correction checks (ZigZag ABC)
f_check_waveB_retracement(float pA_start, float pA_end, float pB_end) =>
float waveA = math.abs(pA_end - pA_start)
float waveB = math.abs(pB_end - pA_end)
float retrace = waveA > 0 ? waveB / waveA : 0
bool inRange = retrace >= 0.38 and retrace <= 0.79
float score = f_in_fib_range(retrace, 0.500, fibTolerance) ? 100.0 : (f_in_fib_range(retrace, 0.618, fibTolerance) ? 90.0 : (inRange ? 70.0 : (retrace >= 0.2 and retrace <= 0.9 ? 40.0 : 10.0)))
[score, retrace]
f_check_waveC_extension(float pA_start, float pA_end, float pB_end, float pC_end) =>
float waveA = math.abs(pA_end - pA_start)
float waveC = math.abs(pC_end - pB_end)
float ratio = waveA > 0 ? waveC / waveA : 0
bool is090 = f_in_fib_range(ratio, 0.900, fibTolerance)
bool is100 = f_in_fib_range(ratio, 1.000, fibTolerance)
bool is127 = f_in_fib_range(ratio, 1.272, fibTolerance)
bool is162 = f_in_fib_range(ratio, 1.618, fibTolerance)
float score = is100 ? 100.0 : (is162 ? 95.0 : (is127 ? 90.0 : (is090 ? 75.0 : (ratio >= 0.8 and ratio <= 1.8 ? 60.0 : 30.0))))
[score, ratio]
// ===== MODULE: DEBUG =====
// 10) DEBUG TABLE
// ───────────────────────────────────────────────────────────────────────────
if showDebug and barstate.islast
int pivotCount = array.size(pivPrice)
int swingCount = array.size(swingAmp)
int patternCount = array.size(detectedPatterns)
// Recreate table on each bar to allow scaling - better contrast
table dt = table.new(position.top_right, 2, 10, color.new(color.black,50), color.new(color.gray,40), 2, color.new(color.gray,40), 2)
table.cell(dt, 0, 0, "Metric", bgcolor=color.new(color.blue,30), text_color=color.white, text_size=size.small)
table.cell(dt, 1, 0, "Value", bgcolor=color.new(color.blue,30), text_color=color.white, text_size=size.small)
table.cell(dt, 0, 1, "Pivots", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 1, str.tostring(pivotCount), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 0, 2, "Swings", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 2, str.tostring(swingCount), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 0, 3, "Dir", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 3, currentDir > 0 ? "UP" : currentDir < 0 ? "DOWN" : "NONE", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 0, 4, "Threshold", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 4, str.tostring(thrAbs, format.mintick), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 0, 5, "Bars Since", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 5, str.tostring(barsSince), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 0, 6, "Retracement", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 6, str.tostring(retracement, format.mintick), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 0, 7, "Patterns", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 7, str.tostring(patternCount), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
if swingCount > 0
float lastAmp = array.get(swingAmp, swingCount - 1)
table.cell(dt, 0, 8, "Last Amp", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 8, str.tostring(lastAmp, format.mintick), text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
if patternCount > 0
PatternCandidate p = array.get(detectedPatterns, patternCount - 1)
table.cell(dt, 0, 9, "Last Pattern", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
table.cell(dt, 1, 9, p.patternType + " (" + str.format("{0,number,#.0}", p.confidence) + "%)", text_size=size.small, text_color=color.white, bgcolor=color.new(color.black,50))
// ═══════════════════════════════════════════════════════════════════════════
// END v1.3 (Sprint 4 Complete)
// Next: Sprint 5 - Output Engine (Auto-Fibs, Zielzonen)
// ═══════════════════════════════════════════════════════════════════════════
// ===== MODULE: CHECKS_DIAGONAL =====
// ═══════════════════════════════════════════════════════════════════════════
// DIAGONAL CHECKS (v2.0 Phase 1)
// ═══════════════════════════════════════════════════════════════════════════
// Leading Diagonal: 5-3-5-3-5 structure, converging trendlines
// Occurs in Wave 1 or Wave A, overlap IS allowed
f_check_leading_diagonal(float p0, float p1, float p2, float p3, float p4, float p5) =>
float w1 = math.abs(p1 - p0)
float w2 = math.abs(p2 - p1)
float w3 = math.abs(p3 - p2)
float w4 = math.abs(p4 - p3)
float w5 = math.abs(p5 - p4)
// Deep retracements (Wave 2/4: 66-81% of previous wave)
float retr2 = w1 > 0 ? w2 / w1 : 0
float retr4 = w3 > 0 ? w4 / w3 : 0
// Progressive shortening (wedge shape)
bool deepRetracements = (retr2 >= 0.66 and retr2 <= 0.85) and (retr4 >= 0.66 and retr4 <= 0.85)
bool shorteningWaves = w5 < w3 and w3 < w1 * 1.8
bool isValid = deepRetracements and shorteningWaves
float confidence = 0.0
if isValid
// Score based on how well it fits
float retrScore = ((retr2 >= 0.70 and retr2 <= 0.79) and (retr4 >= 0.70 and retr4 <= 0.79)) ? 100.0 : 80.0
float shapeScore = (w5 < w3 * 0.9 and w3 < w1 * 1.2) ? 100.0 : 75.0
confidence := (retrScore + shapeScore) / 2.0
else
confidence := 30.0
[isValid, confidence, retr2, retr4]
// Ending Diagonal: 3-3-3-3-3 structure, converging trendlines
// Occurs in Wave 5 or Wave C, overlap IS allowed, strictly converging
f_check_ending_diagonal(float p0, float p1, float p2, float p3, float p4, float p5) =>
float w1 = math.abs(p1 - p0)
float w2 = math.abs(p2 - p1)
float w3 = math.abs(p3 - p2)
float w4 = math.abs(p4 - p3)
float w5 = math.abs(p5 - p4)
// Deep retracements (like Leading)
float retr2 = w1 > 0 ? w2 / w1 : 0
float retr4 = w3 > 0 ? w4 / w3 : 0
// Strict convergence (W5 < W3 < W1)
bool deepRetracements = (retr2 >= 0.66 and retr2 <= 0.85) and (retr4 >= 0.66 and retr4 <= 0.85)
bool strictConverging = w5 < w3 and w3 < w1
bool isValid = deepRetracements and strictConverging
float confidence = 0.0
if isValid
float retrScore = ((retr2 >= 0.70 and retr2 <= 0.79) and (retr4 >= 0.70 and retr4 <= 0.79)) ? 100.0 : 80.0
float convergeScore = (w5 < w3 * 0.85 and w3 < w1 * 0.85) ? 100.0 : 75.0
confidence := (retrScore + convergeScore) / 2.0
else
confidence := 30.0
[isValid, confidence, retr2, retr4]
// ===== MODULE: CHECKS_IMPULSE =====
// Impulse checks (prices are pivot prices p0..p5)
f_check_wave2_retracement(float p0, float p1, float p2) =>
float wave1 = math.abs(p1 - p0)
float wave2 = math.abs(p2 - p1)
float retrace = wave1 > 0 ? wave2 / wave1 : 0
bool in382 = f_in_fib_range(retrace, 0.382, fibTolerance)
bool in500 = f_in_fib_range(retrace, 0.500, fibTolerance)
bool in618 = f_in_fib_range(retrace, 0.618, fibTolerance)
float score = in500 ? 100.0 : (in382 or in618 ? 80.0 : (retrace >= 0.25 and retrace <= 0.75 ? 50.0 : 20.0))
[score, retrace]
f_check_wave4_retracement(float p1, float p2, float p3, float p4) =>
float wave3 = math.abs(p3 - p2)
float wave4 = math.abs(p4 - p3)
float retrace = wave3 > 0 ? wave4 / wave3 : 0
bool in236 = f_in_fib_range(retrace, 0.236, fibTolerance)
bool in382 = f_in_fib_range(retrace, 0.382, fibTolerance)
bool in500 = f_in_fib_range(retrace, 0.500, fibTolerance)
float score = in382 ? 100.0 : ((in236 or in500) ? 80.0 : (retrace >= 0.15 and retrace <= 0.65 ? 50.0 : 20.0))
[score, retrace]
f_check_wave3_extension(float p0, float p1, float p2, float p3) =>
float wave1 = math.abs(p1 - p0)
float wave3 = math.abs(p3 - p2)
float ratio = wave1 > 0 ? wave3 / wave1 : 0
bool is100 = f_in_fib_range(ratio, 1.000, fibTolerance)
bool is162 = f_in_fib_range(ratio, 1.618, fibTolerance)
bool is262 = f_in_fib_range(ratio, 2.618, fibTolerance)
float score = is162 ? 100.0 : (is262 ? 90.0 : (is100 ? 70.0 : (ratio >= 1.0 ? 60.0 : 30.0)))
[score, ratio]
f_check_wave5_extension(float p0, float p1, float p4, float p5) =>
float wave1 = math.abs(p1 - p0)
float wave5 = math.abs(p5 - p4)
float ratio = wave1 > 0 ? wave5 / wave1 : 0
bool is062 = f_in_fib_range(ratio, 0.618, fibTolerance)
bool is100 = f_in_fib_range(ratio, 1.000, fibTolerance)
bool is162 = f_in_fib_range(ratio, 1.618, fibTolerance)
float score = (is062 or is100) ? 100.0 : (is162 ? 80.0 : (ratio >= 0.4 and ratio <= 1.8 ? 60.0 : 30.0))
[score, ratio]
f_check_wave3_not_shortest(float p0, float p1, float p2, float p3, float p4, float p5) =>
float w1 = math.abs(p1 - p0)
float w3 = math.abs(p3 - p2)
float w5 = math.abs(p5 - p4)
bool valid = w3 >= w1 and w3 >= w5
float score = valid ? 100.0 : 0.0
[score, valid]
// ===== MODULE: DETECTION_DIAGONAL =====
// Scan for DIAGONAL patterns (v2.0 Phase 1)
f_detect_diagonal_patterns() =>
int pc = array.size(pivPrice)
if enablePatterns and enableDiagonals and pc >= minPivotsFor5
int startLatest = pc - minPivotsFor5
int startEarliest = math.max(0, startLatest - (windowsBack5 - 1))
int span = startLatest - startEarliest
if span >= 0
for off = 0 to span
int startIdx = startLatest - off
float p0 = f_get_pivot_price(startIdx)
float p1 = f_get_pivot_price(startIdx + 1)
float p2 = f_get_pivot_price(startIdx + 2)
float p3 = f_get_pivot_price(startIdx + 3)
float p4 = f_get_pivot_price(startIdx + 4)
float p5 = f_get_pivot_price(startIdx + 5)
if na(p0) or na(p5)
continue
// Check for Leading Diagonal
[isLeading, leadingConf, retr2L, retr4L] = f_check_leading_diagonal(p0, p1, p2, p3, p4, p5)
if isLeading
c = PatternCandidate.new("LEADING_DIAGONAL", startIdx, startIdx + 5, 6, true, "UNKNOWN", array.new_string(), leadingConf, 100.0, 100.0, 0.0, 100.0, 70.0, 70.0)
bool alternates = f_check_alternation(startIdx, 6)
if not alternates
array.push(c.violations, "No alternation")
if retr2L < 0.66 or retr2L > 0.85
array.push(c.violations, str.format("W2 {0,number,#.##}% (deep)", retr2L*100))
if retr4L < 0.66 or retr4L > 0.85
array.push(c.violations, str.format("W4 {0,number,#.##}% (deep)", retr4L*100))
int dir1 = f_get_pivot_dir(startIdx + 1)
c.direction := dir1 > 0 ? "UP" : "DOWN"
array.push(detectedPatterns, c)
// Check for Ending Diagonal
[isEnding, endingConf, retr2E, retr4E] = f_check_ending_diagonal(p0, p1, p2, p3, p4, p5)
if isEnding
c = PatternCandidate.new("ENDING_DIAGONAL", startIdx, startIdx + 5, 6, true, "UNKNOWN", array.new_string(), endingConf, 100.0, 100.0, 0.0, 100.0, 70.0, 70.0)
bool alternates = f_check_alternation(startIdx, 6)
if not alternates
array.push(c.violations, "No alternation")
if retr2E < 0.66 or retr2E > 0.85
array.push(c.violations, str.format("W2 {0,number,#.##}% (deep)", retr2E*100))
if retr4E < 0.66 or retr4E > 0.85
array.push(c.violations, str.format("W4 {0,number,#.##}% (deep)", retr4E*100))
int dir1 = f_get_pivot_dir(startIdx + 1)
c.direction := dir1 > 0 ? "UP" : "DOWN"
array.push(detectedPatterns, c)
// ===== MODULE: CHECKS_FLAT =====
// ═══════════════════════════════════════════════════════════════════════════
// FLAT CORRECTION CHECKS (v2.0 Phase 2 - Priority 2)
// ═══════════════════════════════════════════════════════════════════════════
// Flat Corrections: 3-3-5 structure (A-B-C where all are corrective)
// Three types: Regular, Expanded, Running
// Flat Correction Validation
// A = 3-wave, B = 3-wave (retraces 90-138% of A), C = 5-wave
f_check_flat_correction(float p0, float p1, float p2, float p3) =>
float waveA = math.abs(p1 - p0)
float waveB = math.abs(p2 - p1)
float waveC = math.abs(p3 - p2)
// B wave must retrace 90-138% of A (key flat characteristic)
float bRetrace = waveA > 0 ? waveB / waveA : 0
bool validBRetrace = bRetrace >= 0.90 and bRetrace <= 1.38
if not validBRetrace
[false, "NONE", 0.0, 0.0, 0.0]
else
// Determine flat type based on B and C relationships
string flatType = "REGULAR"
float confidence = 70.0
// Get direction from Wave A
bool isUp = p1 > p0
// Check Wave B endpoint relative to Wave A start
float bVsA = isUp ? (p2 - p0) / waveA : (p0 - p2) / waveA
// Check Wave C endpoint relative to Wave A start
float cVsA = isUp ? (p0 - p3) / waveA : (p3 - p0) / waveA
// ─────────────────────────────────────────────────────────────────────
// FLAT TYPE CLASSIFICATION:
// ─────────────────────────────────────────────────────────────────────
// Regular Flat: B ≈ A (90-105%), C ≈ A (90-110%)
// Expanded Flat: B > A (100-138%), C > A (110-161.8%)
// Running Flat: B > A (100-138%), C < A (falls short)
// ─────────────────────────────────────────────────────────────────────
if bRetrace >= 1.00 and bRetrace <= 1.38
// B exceeds A start → Check for Expanded or Running
if cVsA >= 1.10 and cVsA <= 1.618
// C breaks beyond A start significantly → EXPANDED FLAT
flatType := "EXPANDED"
// Score based on how well C extends
float cExtensionScore = (cVsA >= 1.272 and cVsA <= 1.382) ? 100.0 : 85.0
float bRetraceScore = (bRetrace >= 1.05 and bRetrace <= 1.15) ? 100.0 : 85.0
confidence := (cExtensionScore + bRetraceScore) / 2.0
else if cVsA < 1.10
// C fails to break significantly → RUNNING FLAT
flatType := "RUNNING"
// Score based on B strength and C weakness
float bRetraceScore = (bRetrace >= 1.10 and bRetrace <= 1.25) ? 100.0 : 85.0
float cFailScore = (cVsA >= 0.90 and cVsA <= 1.05) ? 100.0 : 85.0
confidence := (bRetraceScore + cFailScore) / 2.0
else
// C extends too much → still expanded but lower confidence
flatType := "EXPANDED"
confidence := 75.0
else
// B doesn't exceed A start → REGULAR FLAT
flatType := "REGULAR"
// Score based on equality of A, B, C
float bScore = (bRetrace >= 0.95 and bRetrace <= 1.05) ? 100.0 : 85.0
float cEqualityScore = (cVsA >= 0.90 and cVsA <= 1.10) ? 100.0 : 85.0
confidence := (bScore + cEqualityScore) / 2.0
// Wave C should be roughly equal to or longer than Wave A (for all flats)
bool validCLength = waveC >= waveA * 0.90 and waveC <= waveA * 1.618
if not validCLength
confidence := confidence * 0.8 // Reduce confidence if C length is off
[true, flatType, confidence, bRetrace, cVsA]
// ===== MODULE: PIVOT_ENGINE =====
// 7) PIVOT ENGINE (No-repaint)
// ───────────────────────────────────────────────────────────────────────────
if na(lastExtreme)
lastExtreme := src
lastExtremeIdx:= bar_index
trendStart := src
trendStartIdx := bar_index
currentDir := 0
bool extremeUpdated = false
if currentDir >= 0 and src > lastExtreme
lastExtreme := src
lastExtremeIdx := bar_index
currentDir := 1
extremeUpdated := true
if currentDir <= 0 and src < lastExtreme
lastExtreme := src
lastExtremeIdx := bar_index
currentDir := -1
extremeUpdated := true
float retracement = math.abs(src - lastExtreme)
int barsSince = bar_index - lastExtremeIdx
bool confirmed = retracement >= thrAbs and barsSince >= minSwingBars
if confirmed and not extremeUpdated
// Confirm pivot at last extreme
f_add_pivot(lastExtreme, lastExtremeIdx, currentDir)
f_calc_swing_metrics()
f_draw_zigzag_line()
if showPivots
string labelText = currentDir > 0 ? "H" : "L"
if showMetrics and array.size(swingAmp) > 0
int sidx = array.size(swingAmp) - 1
labelText += str.format("\n{0} | {1}b", str.tostring(array.get(swingAmp, sidx), format.mintick), array.get(swingBars, sidx))
label.new(lastExtremeIdx, lastExtreme, labelText, xloc.bar_index, yloc.price, currentDir > 0 ? colorUp : colorDown, currentDir > 0 ? label.style_label_down : label.style_label_up, color.white, size.small)
// Reset trend tracking and flip direction
trendStart := src
trendStartIdx := bar_index
lastExtreme := src
lastExtremeIdx := bar_index
currentDir := -currentDir
// Detect patterns after each new pivot
if enablePatterns
f_detect_5_wave_patterns()
f_detect_3_wave_patterns()
f_detect_diagonal_patterns() // v2.0 Phase 1
f_detect_flat_patterns() // v2.0 Phase 2 - Priority 2
// ===== MODULE: INIT_POOLS =====
// 6) INITIALIZE DRAWING POOLS (Sprint 5: Persistent Visualization)
// ───────────────────────────────────────────────────────────────────────────
if barstate.isfirst
// Create impulse wave drawing pool
for i = 0 to maxActiveWaves - 1
wd = WaveDrawing.new(line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), label.new(na, na, "0", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "1", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "2", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), label.new(na, na, "3", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "4", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), label.new(na, na, "5", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), label.new(na, na, "Min", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), label.new(na, na, "Ideal", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), label.new(na, na, "Max", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), false, -1, "IMPULSE")
array.push(impulseDrawings, wd)
// Create correction wave drawing pool (with Wave C projections)
for i = 0 to maxActiveWaves - 1
cd = CorrectionDrawing.new(line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), label.new(na, na, "A", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "B", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), label.new(na, na, "C", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "D", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), label.new(na, na, "C Min", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), label.new(na, na, "C Target", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), label.new(na, na, "C Max", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), false, -1)
array.push(correctionDrawings, cd)
// Create diagonal wave drawing pool (v2.0 Phase 1)
for i = 0 to maxActiveWaves - 1
dd = DiagonalDrawing.new(line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na, style=line.style_dashed), line.new(na, na, na, na, xloc=xloc.bar_index, color=na, style=line.style_dashed), label.new(na, na, "0", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "1", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "2", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), label.new(na, na, "3", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "4", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), label.new(na, na, "5", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "Diagonal", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), false, -1, "LEADING")
array.push(diagonalDrawings, dd)
// Create flat correction drawing pool (v2.0 Phase 2 - reuses CorrectionDrawing with projections)
for i = 0 to maxActiveWaves - 1
fd = CorrectionDrawing.new(line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), label.new(na, na, "0", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "A", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), label.new(na, na, "B", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_down), label.new(na, na, "C", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_label_up), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), line.new(na, na, na, na, xloc=xloc.bar_index, color=na), label.new(na, na, "C Min", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), label.new(na, na, "C Target", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), label.new(na, na, "C Max", xloc=xloc.bar_index, yloc=yloc.price, color=color(na), textcolor=na, style=label.style_none), false, -1)
array.push(flatDrawings, fd)
// ===== MODULE: UPDATE_CORRECTION =====
// Helper: Update single correction wave drawing
f_update_correction_drawing(CorrectionDrawing cd, PatternCandidate p, int rank) =>
// DEBUG: Log when this function is called
if showDebug and barstate.islast
log.info("=== CORRECTION DRAWING UPDATE ===")
log.info("Rank: {0}, Pattern: {1}", rank, p.patternType)
log.info("StartIdx: {0}, PivotCount: {1}", p.startIdx, p.pivotCount)
// Get pivot data
pA = f_get_pivot_price(p.startIdx)
pB = f_get_pivot_price(p.startIdx + 1)
pC = f_get_pivot_price(p.startIdx + 2)
pD = f_get_pivot_price(p.startIdx + 3)
xA = f_get_pivot_index(p.startIdx)
xB = f_get_pivot_index(p.startIdx + 1)
xC = f_get_pivot_index(p.startIdx + 2)
xD = f_get_pivot_index(p.startIdx + 3)
dA = f_get_pivot_dir(p.startIdx)
dB = f_get_pivot_dir(p.startIdx + 1)
dC = f_get_pivot_dir(p.startIdx + 2)
dD = f_get_pivot_dir(p.startIdx + 3)
// DEBUG: Log pivot values
if showDebug and barstate.islast
log.info("Pivot A: Price={0}, Index={1}, Dir={2}", pA, xA, dA)
log.info("Pivot B: Price={0}, Index={1}, Dir={2}", pB, xB, dB)
log.info("Pivot C: Price={0}, Index={1}, Dir={2}", pC, xC, dC)
log.info("NA checks: pA={0}, xA={1}, pB={2}, xB={3}, pC={4}, xC={5}", na(pA), na(xA), na(pB), na(xB), na(pC), na(xC))
// Colors
alpha = rank == 0 ? 30 : 70
lineColor = colorCorrection // Orange from settings
labelColor = color.white
bgColor = color(na)
// LARGE labels for better visibility
labelSize = rank == 0 ? size.large : size.normal
suffix = rank > 0 ? "*" : ""
// Update lines
if not na(pB) and not na(xB)
cd.lAB.set_xy1(xA, pA)
cd.lAB.set_xy2(xB, pB)
cd.lAB.set_color(color.new(lineColor, alpha))
cd.lAB.set_width(2)
else
cd.lAB.set_color(na)
if not na(pC) and not na(xC)
cd.lBC.set_xy1(xB, pB)
cd.lBC.set_xy2(xC, pC)
cd.lBC.set_color(color.new(lineColor, alpha))
cd.lBC.set_width(2)
else
cd.lBC.set_color(na)
if not na(pD) and not na(xD)
cd.lCD.set_xy1(xC, pC)
cd.lCD.set_xy2(xD, pD)
cd.lCD.set_color(color.new(lineColor, alpha))
cd.lCD.set_width(2)
else
cd.lCD.set_color(na)
// Update labels with COLORED backgrounds for visibility
color labelBg = rank == 0 ? color.new(lineColor, 80) : color.new(color.gray, 90)
// DEBUG: Log label creation
if showDebug and barstate.islast
log.info("Creating labels with size: {0}, bg color transparency: {1}", labelSize, 80)
if not na(pA) and not na(xA)
cd.bA.set_xy(xA, pA)
cd.bA.set_text("A" + suffix)
cd.bA.set_style(dA > 0 ? label.style_label_down : label.style_label_up)
cd.bA.set_textcolor(color.white)
cd.bA.set_color(labelBg)
cd.bA.set_size(labelSize)
if showDebug and barstate.islast
log.info("✓ Label A created at x={0}, y={1}, text={2}", xA, pA, "A" + suffix)
else
cd.bA.set_textcolor(na)
if showDebug and barstate.islast
log.warning("✗ Label A NOT created - NA values")
if not na(pB) and not na(xB)
cd.bB.set_xy(xB, pB)
cd.bB.set_text("B" + suffix)
cd.bB.set_style(dB > 0 ? label.style_label_down : label.style_label_up)
cd.bB.set_textcolor(color.white)
cd.bB.set_color(labelBg)
cd.bB.set_size(labelSize)
if showDebug and barstate.islast
log.info("✓ Label B created at x={0}, y={1}, text={2}", xB, pB, "B" + suffix)
else
cd.bB.set_textcolor(na)
if showDebug and barstate.islast
log.warning("✗ Label B NOT created - NA values")
if not na(pC) and not na(xC)
cd.bC.set_xy(xC, pC)
cd.bC.set_text("C" + suffix)
cd.bC.set_style(dC > 0 ? label.style_label_down : label.style_label_up)
cd.bC.set_textcolor(color.white)
cd.bC.set_color(labelBg)
cd.bC.set_size(labelSize)
if showDebug and barstate.islast
log.info("✓ Label C created at x={0}, y={1}, text={2}", xC, pC, "C" + suffix)
else
cd.bC.set_textcolor(na)
if showDebug and barstate.islast
log.warning("✗ Label C NOT created - NA values")
// Label D is not used for ABC corrections, hide it
cd.bD.set_textcolor(na)
// ═══════════════════════════════════════════════════════════════════════════
// WAVE C PROJECTION (NEW - Sprint 6 Preview)
// ═══════════════════════════════════════════════════════════════════════════
// Project Wave C target based on Wave A length and Fibonacci ratios
// Standard ABC: C = 0.618×A, 1.0×A, 1.618×A
// ───────────────────────────────────────────────────────────────────────────
if rank == 0 and showProjection and showTargets and not na(pA) and not na(pB) and not na(xB)
// Calculate Wave A amplitude
float waveA = math.abs(pB - pA)
bool isDown = pB < pA // Wave A direction
// Wave C projects from B
// Standard Fibonacci projections for Wave C:
// Min: 0.618 × Wave A
// Ideal: 1.0 × Wave A (equal to A)
// Max: 1.618 × Wave A (extended)
float cMin = isDown ? pB - (waveA * 0.618) : pB + (waveA * 0.618)
float cIdeal = isDown ? pB - (waveA * 1.0) : pB + (waveA * 1.0)
float cMax = isDown ? pB - (waveA * 1.618) : pB + (waveA * 1.618)
// Projection lines extend to the right
int projEndBar = bar_index + 30
int labelBar = bar_index + 15
// Min Target (dotted line)
cd.projMin.set_xy1(xB, cMin)
cd.projMin.set_xy2(projEndBar, cMin)
cd.projMin.set_color(color.new(lineColor, 70))
cd.projMin.set_style(line.style_dotted)
cd.projMin.set_width(1)
// Ideal Target (solid line, prominent)
cd.projIdeal.set_xy1(xB, cIdeal)
cd.projIdeal.set_xy2(projEndBar, cIdeal)
cd.projIdeal.set_color(color.new(lineColor, 20))
cd.projIdeal.set_style(line.style_solid)
cd.projIdeal.set_width(3)
// Max Target (dotted line)
cd.projMax.set_xy1(xB, cMax)
cd.projMax.set_xy2(projEndBar, cMax)
cd.projMax.set_color(color.new(lineColor, 70))
cd.projMax.set_style(line.style_dotted)
cd.projMax.set_width(1)
// Labels with Wave C projection info
cd.projMinLabel.set_xy(labelBar, cMin)
cd.projMinLabel.set_text("C Min: " + str.tostring(cMin, format.mintick) + " [0.618×A]")
cd.projMinLabel.set_textcolor(color.white)
cd.projMinLabel.set_color(color.new(lineColor, 40))
cd.projMinLabel.set_style(label.style_label_left)
cd.projMinLabel.set_size(size.normal)
cd.projIdealLabel.set_xy(labelBar, cIdeal)
cd.projIdealLabel.set_text("🎯 C Target: " + str.tostring(cIdeal, format.mintick) + " [1.0×A]")
cd.projIdealLabel.set_textcolor(color.white)
cd.projIdealLabel.set_color(color.new(lineColor, 10))
cd.projIdealLabel.set_style(label.style_label_left)
cd.projIdealLabel.set_size(size.large)
cd.projMaxLabel.set_xy(labelBar, cMax)
cd.projMaxLabel.set_text("C Max: " + str.tostring(cMax, format.mintick) + " [1.618×A]")
cd.projMaxLabel.set_textcolor(color.white)
cd.projMaxLabel.set_color(color.new(lineColor, 40))
cd.projMaxLabel.set_style(label.style_label_left)
cd.projMaxLabel.set_size(size.normal)
else
// Hide projections if not applicable
cd.projMin.set_color(na)
cd.projIdeal.set_color(na)
cd.projMax.set_color(na)
cd.projMinLabel.set_textcolor(na)
cd.projIdealLabel.set_textcolor(na)
cd.projMaxLabel.set_textcolor(na)
// ===== MODULE: UPDATE_IMPULSE =====
// 8) PERSISTENT VISUALIZATION UPDATE (Sprint 5: Dynamic Updates Every Bar)
// ───────────────────────────────────────────────────────────────────────────
// IMPORTANT NOTE ON ZOOM/PAN BEHAVIOR:
// ───────────────────────────────────────────────────────────────────────────
// PineScript has a fundamental limitation: chart viewport changes (zoom/pan)
// do NOT trigger script re-execution. This means:
//
// ✅ What works:
// ✓ Lines/labels update when NEW BARS appear
// ✓ Coordinates are price-based (yloc.price) so they scale theoretically
// ✓ Live pivot tracking ensures latest pivot moves with price action
//
// ❌ What doesn't work:
// ✓ Pure zoom/pan WITHOUT new bars won't re-render existing objects
// ✓ This is a TradingView platform limitation, not a code issue
//
// 🔧 Workarounds:
// ✓ Wait for next bar to see correct positioning
// ✓ Refresh // - Change timeframe or any input parameter
//
// This behavior is consistent with most professional TradingView indicators.
// ───────────────────────────────────────────────────────────────────────────
// Helper: Update single impulse wave drawing
f_update_impulse_drawing(WaveDrawing wd, PatternCandidate p, int rank) =>
// Get all pivot data
p0 = f_get_pivot_price(p.startIdx)
p1 = f_get_pivot_price(p.startIdx + 1)
p2 = f_get_pivot_price(p.startIdx + 2)
p3 = f_get_pivot_price(p.startIdx + 3)
p4 = f_get_pivot_price(p.startIdx + 4)
p5 = f_get_pivot_price(p.startIdx + 5)
x0 = f_get_pivot_index(p.startIdx)
x1 = f_get_pivot_index(p.startIdx + 1)
x2 = f_get_pivot_index(p.startIdx + 2)
x3 = f_get_pivot_index(p.startIdx + 3)
x4 = f_get_pivot_index(p.startIdx + 4)
x5 = f_get_pivot_index(p.startIdx + 5)
d0 = f_get_pivot_dir(p.startIdx)
d1 = f_get_pivot_dir(p.startIdx + 1)
d2 = f_get_pivot_dir(p.startIdx + 2)
d3 = f_get_pivot_dir(p.startIdx + 3)
d4 = f_get_pivot_dir(p.startIdx + 4)
d5 = f_get_pivot_dir(p.startIdx + 5)
// Colors and styling
alpha = rank == 0 ? 30 : 70
lineColor = p.direction == "UP" ? colorUp : colorDown
labelColor = color.white
bgColor = color(na)
// Update lines (0→1, 1→2, 2→3, 3→4, 4→5)
// IMPORTANT: Always update coordinates, even if they haven't changed
// This forces PineScript to re-render the objects with current chart scale
if not na(p1) and not na(x1)
wd.l01.set_xy1(x0, p0)
wd.l01.set_xy2(x1, p1)
wd.l01.set_color(color.new(lineColor, alpha))
wd.l01.set_width(2)
else
wd.l01.set_color(na)
if not na(p2) and not na(x2)
wd.l12.set_xy1(x1, p1)
wd.l12.set_xy2(x2, p2)
wd.l12.set_color(color.new(lineColor, alpha))
wd.l12.set_width(2)
else
wd.l12.set_color(na)
if not na(p3) and not na(x3)
wd.l23.set_xy1(x2, p2)
wd.l23.set_xy2(x3, p3)
wd.l23.set_color(color.new(lineColor, alpha))
wd.l23.set_width(2)
else
wd.l23.set_color(na)
if not na(p4) and not na(x4)
wd.l34.set_xy1(x3, p3)
wd.l34.set_xy2(x4, p4)
wd.l34.set_color(color.new(lineColor, alpha))
wd.l34.set_width(2)
else
wd.l34.set_color(na)
if not na(p5) and not na(x5)
wd.l45.set_xy1(x4, p4)
wd.l45.set_xy2(x5, p5)
wd.l45.set_color(color.new(lineColor, alpha))
wd.l45.set_width(2)
else
wd.l45.set_color(na)
// Update labels (0, 1, 2, 3, 4, 5)
// LARGE labels for primary pattern, NORMAL for alternatives
labelSize = rank == 0 ? size.large : size.normal
suffix = rank > 0 ? "*" : ""
if not na(p0) and not na(x0)
wd.b0.set_xy(x0, p0)
wd.b0.set_text("0" + suffix)
wd.b0.set_style(d0 > 0 ? label.style_label_down : label.style_label_up)
wd.b0.set_textcolor(labelColor)
wd.b0.set_color(bgColor)
wd.b0.set_size(labelSize)
else
wd.b0.set_textcolor(na)
if not na(p1) and not na(x1)
wd.b1.set_xy(x1, p1)
wd.b1.set_text("1" + suffix)
wd.b1.set_style(d1 > 0 ? label.style_label_down : label.style_label_up)
wd.b1.set_textcolor(labelColor)
wd.b1.set_color(bgColor)
wd.b1.set_size(labelSize)
else
wd.b1.set_textcolor(na)
if not na(p2) and not na(x2)
wd.b2.set_xy(x2, p2)
wd.b2.set_text("2" + suffix)
wd.b2.set_style(d2 > 0 ? label.style_label_down : label.style_label_up)
wd.b2.set_textcolor(labelColor)
wd.b2.set_color(bgColor)
wd.b2.set_size(labelSize)
else
wd.b2.set_textcolor(na)
if not na(p3) and not na(x3)
wd.b3.set_xy(x3, p3)
wd.b3.set_text("3" + suffix)
wd.b3.set_style(d3 > 0 ? label.style_label_down : label.style_label_up)
wd.b3.set_textcolor(labelColor)
wd.b3.set_color(bgColor)
wd.b3.set_size(labelSize)
else
wd.b3.set_textcolor(na)
if not na(p4) and not na(x4)
wd.b4.set_xy(x4, p4)
wd.b4.set_text("4" + suffix)
wd.b4.set_style(d4 > 0 ? label.style_label_down : label.style_label_up)
wd.b4.set_textcolor(labelColor)
wd.b4.set_color(bgColor)
wd.b4.set_size(labelSize)
else
wd.b4.set_textcolor(na)
if not na(p5) and not na(x5)
wd.b5.set_xy(x5, p5)
wd.b5.set_text("5" + suffix)
wd.b5.set_style(d5 > 0 ? label.style_label_down : label.style_label_up)
wd.b5.set_textcolor(labelColor)
wd.b5.set_color(bgColor)
wd.b5.set_size(labelSize)
else
wd.b5.set_textcolor(na)
// Update projections if needed (rank 0 only)
// IMPORTANT: Re-calculate targets on EVERY bar using current pivot prices
// This ensures projections move correctly with zoom/pan and live pivot updates
if rank == 0 and showProjection and showTargets
// Debug logging for projection updates
if showDebug and barstate.islast
log.info("--- Projection Calculation ---")
log.info("Current bar_index: {0}", bar_index)
log.info("P0={0}, P1={1}, P2={2}, P3={3}, P4={4}, P5={5}", p0, p1, p2, p3, p4, p5)
// Projection for Wave 5 (if after Wave 4)
if not na(p4) and not na(x5)
// Re-calculate with current pivot prices (may have changed with live tracking)
[minT, idealT, maxT] = f_calculate_wave_targets("WAVE_5", p.direction == "UP", p0, p1, p2, p3, p4)
string scenario = f_get_projection_scenario("WAVE_5", p0, p1, p2, p3, p4)
if showDebug and barstate.islast
log.info("Wave 5 Projection: Min={0}, Ideal={1}, Max={2}", minT, idealT, maxT)
log.info("Scenario: {0}", scenario)
if not na(idealT)
// Use current bar_index for dynamic positioning
int projEndBar = bar_index + 30
int labelBar = bar_index + 15
// Min Target (dashed line)
wd.projMin.set_xy1(x5, minT)
wd.projMin.set_xy2(projEndBar, minT)
wd.projMin.set_color(color.new(lineColor, 70))
wd.projMin.set_style(line.style_dotted)
wd.projMin.set_width(1)
// Ideal Target (solid line, prominent)
wd.projIdeal.set_xy1(x5, idealT)
wd.projIdeal.set_xy2(projEndBar, idealT)
wd.projIdeal.set_color(color.new(lineColor, 20))
wd.projIdeal.set_style(line.style_solid)
wd.projIdeal.set_width(3)
// Max Target (dashed line)
wd.projMax.set_xy1(x5, maxT)
wd.projMax.set_xy2(projEndBar, maxT)
wd.projMax.set_color(color.new(lineColor, 70))
wd.projMax.set_style(line.style_dotted)
wd.projMax.set_width(1)
// Labels with scenario info - LARGER for better visibility
wd.projMinLabel.set_xy(labelBar, minT)
wd.projMinLabel.set_text("W5 Min: " + str.tostring(minT, format.mintick))
wd.projMinLabel.set_textcolor(color.white)
wd.projMinLabel.set_color(color.new(lineColor, 40))
wd.projMinLabel.set_style(label.style_label_left)
wd.projMinLabel.set_size(size.normal)
wd.projIdealLabel.set_xy(labelBar, idealT)
wd.projIdealLabel.set_text("🎯 W5 Target: " + str.tostring(idealT, format.mintick) + " [" + scenario + "]")
wd.projIdealLabel.set_textcolor(color.white)
wd.projIdealLabel.set_color(color.new(lineColor, 10))
wd.projIdealLabel.set_style(label.style_label_left)
wd.projIdealLabel.set_size(size.large)
wd.projMaxLabel.set_xy(labelBar, maxT)
wd.projMaxLabel.set_text("W5 Max: " + str.tostring(maxT, format.mintick))
wd.projMaxLabel.set_textcolor(color.white)
wd.projMaxLabel.set_color(color.new(lineColor, 40))
wd.projMaxLabel.set_style(label.style_label_left)
wd.projMaxLabel.set_size(size.normal)
else
wd.projMin.set_color(na)
wd.projIdeal.set_color(na)
wd.projMax.set_color(na)
wd.projMinLabel.set_textcolor(na)
wd.projIdealLabel.set_textcolor(na)
wd.projMaxLabel.set_textcolor(na)
// Projection for Wave 3 (if after Wave 2, before or at Wave 3)
else if not na(p2) and not na(x2)
// Re-calculate with current pivot prices
[minT, idealT, maxT] = f_calculate_wave_targets("WAVE_3", p.direction == "UP", p0, p1, p2, na, na)
string scenario = f_get_projection_scenario("WAVE_3", p0, p1, p2, na, na)
if showDebug and barstate.islast
log.info("Wave 3 Projection: Min={0}, Ideal={1}, Max={2}", minT, idealT, maxT)
log.info("Scenario: {0}", scenario)
if not na(idealT)
int projEndBar = bar_index + 30
int labelBar = bar_index + 15
// Min Target (dashed line)
wd.projMin.set_xy1(x2, minT)
wd.projMin.set_xy2(projEndBar, minT)
wd.projMin.set_color(color.new(lineColor, 70))
wd.projMin.set_style(line.style_dotted)
wd.projMin.set_width(1)
// Ideal Target (solid line, prominent)
wd.projIdeal.set_xy1(x2, idealT)
wd.projIdeal.set_xy2(projEndBar, idealT)
wd.projIdeal.set_color(color.new(lineColor, 20))
wd.projIdeal.set_style(line.style_solid)
wd.projIdeal.set_width(3)
// Max Target (dashed line)
wd.projMax.set_xy1(x2, maxT)
wd.projMax.set_xy2(projEndBar, maxT)
wd.projMax.set_color(color.new(lineColor, 70))
wd.projMax.set_style(line.style_dotted)
wd.projMax.set_width(1)
// Labels with scenario info - LARGER for better visibility
wd.projMinLabel.set_xy(labelBar, minT)
wd.projMinLabel.set_text("W3 Min: " + str.tostring(minT, format.mintick))
wd.projMinLabel.set_textcolor(color.white)
wd.projMinLabel.set_color(color.new(lineColor, 40))
wd.projMinLabel.set_style(label.style_label_left)
wd.projMinLabel.set_size(size.normal)
wd.projIdealLabel.set_xy(labelBar, idealT)
wd.projIdealLabel.set_text("🎯 W3 Target: " + str.tostring(idealT, format.mintick) + " [" + scenario + "]")
wd.projIdealLabel.set_textcolor(color.white)
wd.projIdealLabel.set_color(color.new(lineColor, 10))
wd.projIdealLabel.set_style(label.style_label_left)
wd.projIdealLabel.set_size(size.large)
wd.projMaxLabel.set_xy(labelBar, maxT)
wd.projMaxLabel.set_text("W3 Max: " + str.tostring(maxT, format.mintick))
wd.projMaxLabel.set_textcolor(color.white)
wd.projMaxLabel.set_color(color.new(lineColor, 40))
wd.projMaxLabel.set_style(label.style_label_left)
wd.projMaxLabel.set_size(size.normal)
else
wd.projMin.set_color(na)
wd.projIdeal.set_color(na)
wd.projMax.set_color(na)
wd.projMinLabel.set_textcolor(na)
wd.projIdealLabel.set_textcolor(na)
wd.projMaxLabel.set_textcolor(na)
else
wd.projMin.set_color(na)
wd.projIdeal.set_color(na)
wd.projMax.set_color(na)
wd.projMinLabel.set_textcolor(na)
wd.projIdealLabel.set_textcolor(na)
wd.projMaxLabel.set_textcolor(na)
else
wd.projMin.set_color(na)
wd.projIdeal.set_color(na)
wd.projMax.set_color(na)
wd.projMinLabel.set_textcolor(na)
wd.projIdealLabel.set_textcolor(na)
wd.projMaxLabel.set_textcolor(na)
// ===== MODULE: DETECTION_CORRECTION =====
// Scan sliding windows for 3-wave correction (ABC)
f_detect_3_wave_patterns() =>
int pc = array.size(pivPrice)
if enablePatterns and pc >= minPivotsFor3
int startLatest = pc - minPivotsFor3
int startEarliest = math.max(0, startLatest - (windowsBack3 - 1))
// Only run loop if we have valid range
int span = startLatest - startEarliest
if span >= 0
// iterate with positive step using offset
for off = 0 to span
int startIdx = startLatest - off
float pA0 = f_get_pivot_price(startIdx)
float pA1 = f_get_pivot_price(startIdx + 1)
float pB1 = f_get_pivot_price(startIdx + 2)
float pC1 = f_get_pivot_price(startIdx + 3)
if na(pA0) or na(pC1)
continue
c = PatternCandidate.new("CORRECTION", startIdx, startIdx + 3, 4, false, "UNKNOWN", array.new_string(), 0.0, 0.0, 0.0, 0.0, 100.0, 70.0, 70.0)
bool alternates = f_check_alternation(startIdx, 4)
if not alternates
array.push(c.violations, "No alternation")
c.structureScore := alternates ? 100.0 : 30.0
if enableFibChecks
[scoreB, retrB] = f_check_waveB_retracement(pA0, pA1, pB1)
[scoreC, ratioC] = f_check_waveC_extension(pA0, pA1, pB1, pC1)
c.retracementScore := scoreB
c.extensionScore := scoreC
if retrB < 0.30 or retrB > 0.85
array.push(c.violations, str.format("B {0,number,#.##}%", retrB*100))
if ratioC < 0.7 or ratioC > 2.0
array.push(c.violations, str.format("C={0,number,#.##}×A", ratioC))
int dir1 = f_get_pivot_dir(startIdx + 1)
c.direction := dir1 > 0 ? "UP" : "DOWN"
c.isValid := alternates
c.confidence := (c.structureScore * 0.35) + (c.retracementScore * 0.25) + (c.overlapScore * 0.15) + (c.extensionScore * 0.08) + (c.timeScore * 0.07) + (c.momentumScore * 0.10)
array.push(detectedPatterns, c)
// ===== MODULE: UPDATE_HIDE =====
// Helper: Hide a wave drawing
f_hide_impulse_drawing(WaveDrawing wd) =>
wd.l01.set_color(na)
wd.l12.set_color(na)
wd.l23.set_color(na)
wd.l34.set_color(na)
wd.l45.set_color(na)
wd.b0.set_textcolor(na)
wd.b1.set_textcolor(na)
wd.b2.set_textcolor(na)
wd.b3.set_textcolor(na)
wd.b4.set_textcolor(na)
wd.b5.set_textcolor(na)
wd.projMin.set_color(na)
wd.projIdeal.set_color(na)
wd.projMax.set_color(na)
wd.projMinLabel.set_textcolor(na)
wd.projIdealLabel.set_textcolor(na)
wd.projMaxLabel.set_textcolor(na)
f_hide_correction_drawing(CorrectionDrawing cd) =>
cd.lAB.set_color(na)
cd.lBC.set_color(na)
cd.lCD.set_color(na)
cd.bA.set_textcolor(na)
cd.bB.set_textcolor(na)
cd.bC.set_textcolor(na)
cd.bD.set_textcolor(na)
// Hide Wave C projections
cd.projMin.set_color(na)
cd.projIdeal.set_color(na)
cd.projMax.set_color(na)
cd.projMinLabel.set_textcolor(na)
cd.projIdealLabel.set_textcolor(na)
cd.projMaxLabel.set_textcolor(na)
// ===== MODULE: SERIES =====
// 2) SERIES & THRESHOLDS
// ───────────────────────────────────────────────────────────────────────────
float src = smoothLen > 0 ? ta.sma(hl2, smoothLen) : hl2
float atrVal = ta.atr(atrLen)
float thrAbs = switch mode
"ATR" => atrMult * atrVal
"Percent" => percentThr / 100.0 * close
=> atrMult * atrVal
// Momentum series (global)
float rsiSeries = ta.rsi(close, momentumLen)
[macdLine, macdSignal, macdHist] = ta.macd(close, 12, 26, 9)
// ===== MODULE: UPDATE_DIAGONAL =====
// Helper: Update single diagonal wave drawing (v2.0 Phase 1)
f_update_diagonal_drawing(DiagonalDrawing dd, PatternCandidate p, int rank) =>
// Get all pivot data
p0 = f_get_pivot_price(p.startIdx)
p1 = f_get_pivot_price(p.startIdx + 1)
p2 = f_get_pivot_price(p.startIdx + 2)
p3 = f_get_pivot_price(p.startIdx + 3)
p4 = f_get_pivot_price(p.startIdx + 4)
p5 = f_get_pivot_price(p.startIdx + 5)
x0 = f_get_pivot_index(p.startIdx)
x1 = f_get_pivot_index(p.startIdx + 1)
x2 = f_get_pivot_index(p.startIdx + 2)
x3 = f_get_pivot_index(p.startIdx + 3)
x4 = f_get_pivot_index(p.startIdx + 4)
x5 = f_get_pivot_index(p.startIdx + 5)
d0 = f_get_pivot_dir(p.startIdx)
d1 = f_get_pivot_dir(p.startIdx + 1)
d2 = f_get_pivot_dir(p.startIdx + 2)
d3 = f_get_pivot_dir(p.startIdx + 3)
d4 = f_get_pivot_dir(p.startIdx + 4)
d5 = f_get_pivot_dir(p.startIdx + 5)
// Colors and styling (purple for diagonals)
alpha = rank == 0 ? 30 : 70
lineColor = color.new(#9C27B0, 0) // Purple for diagonals
labelColor = color.white
bgColor = color(na)
labelSize = rank == 0 ? size.normal : size.small
suffix = rank > 0 ? "*" : ""
// Update wave lines (0→1, 1→2, 2→3, 3→4, 4→5)
if not na(p1) and not na(x1)
dd.l01.set_xy1(x0, p0)
dd.l01.set_xy2(x1, p1)
dd.l01.set_color(color.new(lineColor, alpha))
dd.l01.set_width(2)
else
dd.l01.set_color(na)
if not na(p2) and not na(x2)
dd.l12.set_xy1(x1, p1)
dd.l12.set_xy2(x2, p2)
dd.l12.set_color(color.new(lineColor, alpha))
dd.l12.set_width(2)
else
dd.l12.set_color(na)
if not na(p3) and not na(x3)
dd.l23.set_xy1(x2, p2)
dd.l23.set_xy2(x3, p3)
dd.l23.set_color(color.new(lineColor, alpha))
dd.l23.set_width(2)
else
dd.l23.set_color(na)
if not na(p4) and not na(x4)
dd.l34.set_xy1(x3, p3)
dd.l34.set_xy2(x4, p4)
dd.l34.set_color(color.new(lineColor, alpha))
dd.l34.set_width(2)
else
dd.l34.set_color(na)
if not na(p5) and not na(x5)
dd.l45.set_xy1(x4, p4)
dd.l45.set_xy2(x5, p5)
dd.l45.set_color(color.new(lineColor, alpha))
dd.l45.set_width(2)
else
dd.l45.set_color(na)
// Update wave labels (0, 1, 2, 3, 4, 5)
if not na(p0) and not na(x0)
dd.b0.set_xy(x0, p0)
dd.b0.set_text("0" + suffix)
dd.b0.set_style(d0 > 0 ? label.style_label_down : label.style_label_up)
dd.b0.set_textcolor(labelColor)
dd.b0.set_color(bgColor)
dd.b0.set_size(labelSize)
else
dd.b0.set_textcolor(na)
if not na(p1) and not na(x1)
dd.b1.set_xy(x1, p1)
dd.b1.set_text("1" + suffix)
dd.b1.set_style(d1 > 0 ? label.style_label_down : label.style_label_up)
dd.b1.set_textcolor(labelColor)
dd.b1.set_color(bgColor)
dd.b1.set_size(labelSize)
else
dd.b1.set_textcolor(na)
if not na(p2) and not na(x2)
dd.b2.set_xy(x2, p2)
dd.b2.set_text("2" + suffix)
dd.b2.set_style(d2 > 0 ? label.style_label_down : label.style_label_up)
dd.b2.set_textcolor(labelColor)
dd.b2.set_color(bgColor)
dd.b2.set_size(labelSize)
else
dd.b2.set_textcolor(na)
if not na(p3) and not na(x3)
dd.b3.set_xy(x3, p3)
dd.b3.set_text("3" + suffix)
dd.b3.set_style(d3 > 0 ? label.style_label_down : label.style_label_up)
dd.b3.set_textcolor(labelColor)
dd.b3.set_color(bgColor)
dd.b3.set_size(labelSize)
else
dd.b3.set_textcolor(na)
if not na(p4) and not na(x4)
dd.b4.set_xy(x4, p4)
dd.b4.set_text("4" + suffix)
dd.b4.set_style(d4 > 0 ? label.style_label_down : label.style_label_up)
dd.b4.set_textcolor(labelColor)
dd.b4.set_color(bgColor)
dd.b4.set_size(labelSize)
else
dd.b4.set_textcolor(na)
if not na(p5) and not na(x5)
dd.b5.set_xy(x5, p5)
dd.b5.set_text("5" + suffix)
dd.b5.set_style(d5 > 0 ? label.style_label_down : label.style_label_up)
dd.b5.set_textcolor(labelColor)
dd.b5.set_color(bgColor)
dd.b5.set_size(labelSize)
else
dd.b5.set_textcolor(na)
// Draw converging trendlines (wedge shape)
// ═════════════════════════════════════════════════════════════════════════
// TRENDLINE LOGIC:
// - Upper trendline connects the peaks (direction-dependent)
// - Lower trendline connects the troughs (direction-dependent)
// - Extended 15 bars beyond Wave 5 to show convergence clearly
// ═════════════════════════════════════════════════════════════════════════
if not na(p0) and not na(p2) and not na(p4) and not na(x0) and not na(x2) and not na(x4)
// Calculate extended endpoint for visual clarity
int extendBars = 15
int extendedX = x5 + extendBars
if p.direction == "UP"
// BULLISH DIAGONAL:
// Upper boundary: 1-3-5 (highs) - slopes upward
// Lower boundary: 0-2-4 (lows) - slopes upward but slower (converging)
// Calculate slope for upper trendline (1→5)
float upperSlope = not na(x5) and (x5 - x1) != 0 ? (p5 - p1) / (x5 - x1) : 0
float extendedUpperY = p5 + (upperSlope * extendBars)
dd.trendLine1.set_xy1(x1, p1)
dd.trendLine1.set_xy2(extendedX, extendedUpperY)
// Calculate slope for lower trendline (0→4)
float lowerSlope = not na(x4) and (x4 - x0) != 0 ? (p4 - p0) / (x4 - x0) : 0
float extendedLowerY = p4 + (lowerSlope * extendBars)
dd.trendLine2.set_xy1(x0, p0)
dd.trendLine2.set_xy2(extendedX, extendedLowerY)
else
// BEARISH DIAGONAL:
// Upper boundary: 0-2-4 (highs) - slopes downward
// Lower boundary: 1-3-5 (lows) - slopes downward faster (converging)
// Calculate slope for upper trendline (0→4)
float upperSlope = not na(x4) and (x4 - x0) != 0 ? (p4 - p0) / (x4 - x0) : 0
float extendedUpperY = p4 + (upperSlope * extendBars)
dd.trendLine1.set_xy1(x0, p0)
dd.trendLine1.set_xy2(extendedX, extendedUpperY)
// Calculate slope for lower trendline (1→5)
float lowerSlope = not na(x5) and (x5 - x1) != 0 ? (p5 - p1) / (x5 - x1) : 0
float extendedLowerY = p5 + (lowerSlope * extendBars)
dd.trendLine2.set_xy1(x1, p1)
dd.trendLine2.set_xy2(extendedX, extendedLowerY)
// Style the trendlines (dashed, semi-transparent purple)
dd.trendLine1.set_color(color.new(lineColor, 40)) // More visible than 50
dd.trendLine1.set_style(line.style_dashed)
dd.trendLine1.set_width(2) // Thicker for better visibility
dd.trendLine2.set_color(color.new(lineColor, 40))
dd.trendLine2.set_style(line.style_dashed)
dd.trendLine2.set_width(2)
else
dd.trendLine1.set_color(na)
dd.trendLine2.set_color(na)
// Type label ("Leading Diagonal" or "Ending Diagonal")
// ═════════════════════════════════════════════════════════════════════════
// Display diagonal type with confidence score
// ═════════════════════════════════════════════════════════════════════════
if not na(p5) and not na(x5)
string diagonalType = p.patternType == "LEADING_DIAGONAL" ? "Leading Diagonal" : "Ending Diagonal"
string confidenceStr = str.format("{0,number,#}%", p.confidence)
string typeText = diagonalType + " (" + confidenceStr + ")"
if rank > 0
typeText += "*"
// Position label slightly offset from Wave 5
int labelOffset = 3
int labelX = x5 + labelOffset
dd.typeLabel.set_xy(labelX, p5)
dd.typeLabel.set_text(typeText)
dd.typeLabel.set_style(d5 > 0 ? label.style_label_down : label.style_label_up)
dd.typeLabel.set_textcolor(labelColor)
dd.typeLabel.set_color(color.new(#9C27B0, rank == 0 ? 60 : 80)) // More opaque for primary
dd.typeLabel.set_size(rank == 0 ? size.normal : size.small)
else
dd.typeLabel.set_textcolor(na)
f_hide_diagonal_drawing(DiagonalDrawing dd) =>
dd.l01.set_color(na)
dd.l12.set_color(na)
dd.l23.set_color(na)
dd.l34.set_color(na)
dd.l45.set_color(na)
dd.trendLine1.set_color(na)
dd.trendLine2.set_color(na)
dd.b0.set_textcolor(na)
dd.b1.set_textcolor(na)
dd.b2.set_textcolor(na)
dd.b3.set_textcolor(na)
dd.b4.set_textcolor(na)
dd.b5.set_textcolor(na)
dd.typeLabel.set_textcolor(na)
// ============================================================================
// BUILD INFORMATION
// ============================================================================
// Project: wave-navigator
// Modules: 22 project + 4 shared
// Build System: Modular Pine Builder v2.0
// Build Date: 2025-10-13 02:06:31
// Source: config.json + modules/
// ============================================================================
Overview
Wave Navigator [WavesUnchained] scores candidates with optional Fibonacci, time-ratio, RSI or MACD momentum and volume checks, then draws the top-ranked patterns, pivot labels, zigzag lines, swing metrics and a confidence box. Settings control swing length, pattern windows, strict wave-three rules and the minimum display score. It does not place trades or define exits.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Detection Mode
string
ATR
mode
ATR Length
int
14
atrLen
ATR Multiplier
float
2.0
atrMult
Percent Threshold
float
2.0
percentThr
Min Swing Bars
int
10
minSwingBars
Smoothing Length
int
3
smoothLen
Max Pivots Stored
int
21
pivotsToKeep
Enable Pattern Detection
bool
true
enablePatterns
Min Pivots for 5-Wave
int
6
minPivotsFor5
Min Pivots for 3-Wave
int
4
minPivotsFor3
Scan back 5-wave windows
int
6
windowsBack5
Scan back 3-wave windows
int
6
windowsBack3
Show all 37 settings
Setting
Type
Default
Variable
Enable Diagonal Detection
bool
true
enableDiagonals
Enable Flat Corrections
bool
true
enableFlats
Show Pattern Labels
bool
true
showPatternLabels
Show Pattern Info
bool
true
showPatternInfo
Fibonacci Tolerance
float
0.10
fibTolerance
Strict Wave 3 Rule
bool
true
strictWave3
Check Time Ratios
bool
true
checkTimeRatios
Enable Fibonacci Checks
bool
true
enableFibChecks
Min Score for Display
float
60.0
minScoreThreshold
Enable Momentum Checks
bool
true
enableMomentum
Momentum Indicator
string
RSI
momentumType
Momentum Length
int
14
momentumLen
Enable Volume Checks
bool
true
enableVolume
Show Top N Patterns
int
2
showTopPatterns
Show Pivot Labels
bool
true
showPivots
Draw ZigZag Lines
bool
true
showZigZag
Show Swing Metrics
bool
true
showMetrics
Show Confidence Box
bool
true
showConfBox
Up Swing Color
color
color.new(#2979FF, 0)
colorUp
Down Swing Color
color
color.new(#FF6B00, 0)
colorDown
Correction Color
color
color.new(#FF9800, 0)
colorCorrection
Debug Mode
bool
false
showDebug
Show Wave Projection
bool
true
showProjection
Show Target Levels
bool
true
showTargets
Info Table Position
string
Middle Right
infoPosition
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.
This overlay indicator marks call and put setups from EMAs, RSI, MACD, and a SPY volume ratio. A call needs higher highs, RSI above its minimum, rising MACD, above-average proxy volume, and a rising trend EMA; a put reverses those tests. Early markers use an E
This overlay indicator marks volume-driven buying and selling pressure on the price chart. It compares volume with a simple moving average scaled by a volatility-based multiplier, and it uses a trend simple moving average plus RSI thresholds to color bars. Swi
This overlay indicator screens a pasted list of up to forty symbols and draws a table on the price chart. It classifies bullish or bearish setups from the percent change between the current candle and the candle 250 bars back, then shows RSI with a simple movi
This indicator calculates pivot high and pivot low points on the price chart based on configurable left and right bar counts. Confirmed pivot highs and lows are plotted with negative bar offsets directly on the price chart using line and cross styles. Addition
This overlay indicator draws a fixed-range volume profile on the price chart, splitting traded activity into up and down volume at each price level. It also plots a sentiment profile, a developing point of control, and value-area high and low around a configur
The MGIC-tracker is a TradingView indicator that overlays ARK Invest trade records onto stock price charts. It plots buy and sell labels at the exact timestamp of each transaction, showing share volume and ETF weight. Users select a specific ETF type via setti