Indicator · Pine Script v6 · On the chart

Kalman Trailing Stop

by vedkanani123· code credited to fdx101

A price-chart indicator that builds a Kalman-filter baseline, optionally weighted by volume, and trails a stop at a standard-deviation distance, flipping when price penetrates it and the baseline slope agrees.

View on GitHub

GitHub stars1on the repository
Pine versionv6current
Lines of code820
Settings28inputs
LicenceNone statedfile: MPL-2.0
Last update2 months ago23 Aug 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

820 lines of Pine Script v6 from indicator/olg_7_6_26/ALL_2/codex/indicator/Kalman Trailing Stop (KTS)_20.txt, published under MPL-2.0 (file header). Keep the author's credit and licence lines when you reuse it.

Kalman Trailing Stop (KTS)_20.txt
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © fdx101
// Upgraded with Kalman Adaptive Trailing Stop & Statistical DSP Plunger Logic.

//@version=6
indicator('Kalman Trailing Stop', overlay = true, shorttitle = "KTS", max_bars_back = 5000)

// --- Inputs ---
group_cycle = "Cycle Configuration"
dominantCyclePeriod = input.int(28, 'Dominant Cycle Period', minval = 5, tooltip = 'The wavelength of the dominant market cycle (e.g., synchronized with CSM).', group = group_cycle)

group_plunger = "Plunger Settings"
closePercentage = input.float(30, 'Wick Filter %', minval = 1, maxval = 50, step = 1, tooltip = 'The close must be within this percentage of the bar\'s range from the extreme.', group = group_plunger) / 100
base_sweep_mult = input.float(1.5, 'Statistical Sweep Multiplier (SD)', minval = 0.5, step=0.1, group = group_plunger, tooltip = "How far outside the Kalman Band the wick must pierce to be considered a true mathematical liquidity sweep.")

group_kf = "Kalman Trailing Stop Settings"
kf_speed = input.int(50, title = 'Kalman Responsiveness (1-100)', minval = 1, maxval = 100, group = group_kf, tooltip = "How fast the Trailing Stop reacts to price changes.")
use_vol_weighting = input.bool(true, title="Volume-Weighted Variance", group=group_kf, tooltip="Dynamically adjusts Kalman responsiveness based on volume. High volume = high trust (fast reaction). Low volume = low trust (flat baseline).")
kf_noise_len = input.int(100, title = 'Noise Variance Window', minval = 10, maxval = 200, group = group_kf)
kf_smooth_len = input.int(5, title='Kalman Smoothing (DEMA)', minval=1, maxval=20, group=group_kf, tooltip="Length of the DEMA smoother applied to the raw Kalman output.")
use_slope_confirm = input.bool(true, title='Use Kalman Slope Confirmation', group=group_kf, tooltip="Only flip the trailing stop if the Kalman baseline slope has also flipped, preventing fake-outs.")
mu_offset = input.float(0.0, "Signal Offset (μ in ATRs)", step=0.1, group=group_kf, tooltip="Trend flip requires price to penetrate the Kalman trailing stop by this many ATRs. Acts as a noise buffer against fake-outs.")
push_factor = input.float(0.2, title="Trend Agreement Push (ATR %)", minval=0.0, maxval=1.0, step=0.05, group=group_kf, tooltip="Actively pushes the trailing stop closer to price when momentum and market structure agree.")
use_sigmoid_smoothing = input.bool(true, title="Use Sigmoid Smoothing", group=group_kf, tooltip="Smooths out jagged vertical jumps in the trailing stop by gliding to the new structural floor.")
sig_len = input.int(1, "Sigmoid Transition Length", group=group_kf)
stop_mult = input.float(3.0, title = 'Trailing Stop Distance (SD)', minval = 0.5, step = 0.1, group = group_kf)
relax_factor = input.float(0.1, 'Volatility Relaxation %', minval=0.0, maxval=1.0, step=0.1, group=group_kf, tooltip="How much the stop is allowed to relax (widen) when market noise suddenly expands. 0.0 = Strict rigid ratchet. 0.5 = Absorbs 50% of volatility shocks.")

group_kf3d = "KF4 3D Oscillator (Mean Reversion)"
use_3d_osc = input.bool(false, "Enable 3D Oscillator State (KF4)", group=group_kf3d, tooltip="Upgrades the Kalman filter to a 3D state tracking Price, Velocity, and an Extremum Oscillator. Useful for scalping or mean-reverting assets to tighten stops at extremes.")
osc_len = input.int(14, "Oscillator Lookback Length", group=group_kf3d)

group_risk = "Risk & Position Sizing"
use_tp = input.bool(true, "Use Take Profit", group=group_risk, tooltip="Enable ATR-based Take Profit.")
tp_mult = input.float(4.0, "Take Profit Multiplier (ATR)", minval=0.5, step=0.1, group=group_risk)
account_size = input.float(10000, "Account Size ($)", group=group_risk)
risk_percent = input.float(2.0, "Risk Per Trade (%)", minval=0.1, step=0.1, group=group_risk) / 100.0
pyramid_factor = input.float(0.5, "Pyramid Size Multiplier", minval=0.1, step=0.1, maxval=1.0, tooltip="Multiplier for scale-in entries. 0.5 = Half Size.", group=group_risk)
max_leverage = input.float(1.0, "Max Account Leverage", minval=0.1, step=0.5, tooltip="1.0 = Cash account. 2.0 = Reg T Margin. 4.0 = Day Trade Margin. Caps position size to prevent impossible orders when stop loss is extremely tight.", group=group_risk)

group_display = "Display Settings"
showBullish = input.bool(false, 'Show Bullish Signals', group = group_display)
showBearish = input.bool(false, 'Show Bearish Signals', group = group_display)
show_williams_ms = input.bool(false, 'Show Market Structure (ITH/ITL)', group = group_display)

//--- Intraday Trading Mode
grp_intraday = 'Intraday Trading Mode'
useIntraday = input.bool(false, 'Enable Intraday Filter', group = grp_intraday, tooltip = 'Filters out overnight gaps from the Monte Carlo simulation by enforcing a trading session.')
sessionStr = input.session("0945-1550", 'Trading Session Window', group = grp_intraday)
sessionTz = input.string("America/New_York", 'Session Timezone', group = grp_intraday)

bool inSession = not useIntraday or not na(time(timeframe.period, sessionStr, sessionTz))

// --- Types ---

type Swing
    int idx
    float price
    bool isHigh

// --- Resolution of Parameters based on Dominant Cycle ---
atrLength = dominantCyclePeriod
plungerLength = math.max(5, math.round(dominantCyclePeriod * 0.35))

// --- Information Filter (Canonical Parameterization) ---
// Highly numerically stable against 64-bit float precision drift compared to standard Kalman
f_kalman2d(float val, float noise_variance, float system_variance, float r_vol, bool use_vol) =>
    var float x0 = na // Level
    var float x1 = na // Velocity
    var float p00 = na
    var float p01 = na
    var float p10 = na
    var float p11 = na
    
    if not na(val) and not na(noise_variance) and not na(system_variance)
        if na(x0)
            x0 := val
            x1 := 0.0
            p00 := 1.0
            p01 := 0.0
            p10 := 0.0
            p11 := 1.0
        else
            float q0 = system_variance
            float q1 = system_variance * 0.1
            
            float R = use_vol and r_vol > 0.0 ? math.max(noise_variance / r_vol, 1e-6) : math.max(noise_variance, 1e-6)
            
            // PREDICT STEP
            float x_pred_0 = x0 + x1
            float x_pred_1 = x1
            
            float p_pred_00 = p00 + p01 + p10 + p11 + q0
            float p_pred_01 = p01 + p11
            float p_pred_10 = p10 + p11
            float p_pred_11 = p11 + q1
            
            // UPDATE STEP
            float y = val - x_pred_0
            float S = p_pred_00 + R
            
            float k0 = p_pred_00 / S
            float k1 = p_pred_10 / S
            
            x0 := x_pred_0 + k0 * y
            x1 := x_pred_1 + k1 * y
            
            float new_p00 = (1.0 - k0) * p_pred_00
            float new_p01 = (1.0 - k0) * p_pred_01
            float new_p10 = -k1 * p_pred_00 + p_pred_10
            float new_p11 = -k1 * p_pred_01 + p_pred_11
            
            p00 := new_p00
            p01 := new_p01
            p10 := new_p10
            p11 := new_p11
            
    [x0, x1, p00 + p11]

// --- 3D Information Filter (KF4) ---
f_kalman3d(float val, float osc_val, float noise_variance, float osc_variance, float system_variance, float r_vol, bool use_vol) =>
    var float x0 = na // Level
    var float x1 = na // Velocity
    var float x2 = na // Oscillator
    
    var float p00 = na
    var float p01 = na
    var float p02 = na
    var float p10 = na
    var float p11 = na
    var float p12 = na
    var float p20 = na
    var float p21 = na
    var float p22 = na
    
    if not na(val) and not na(osc_val) and not na(noise_variance) and not na(osc_variance) and not na(system_variance)
        if na(x0)
            x0 := val
            x1 := 0.0
            x2 := osc_val
            p00 := 1.0
            p01 := 0.0
            p02 := 0.0
            p10 := 0.0
            p11 := 1.0
            p12 := 0.0
            p20 := 0.0
            p21 := 0.0
            p22 := 1.0
        else
            float q0 = system_variance
            float q1 = system_variance * 0.1
            float q2 = system_variance
            
            float R0 = use_vol and r_vol > 0.0 ? math.max(noise_variance / r_vol, 1e-6) : math.max(noise_variance, 1e-6)
            float R1 = use_vol and r_vol > 0.0 ? math.max(osc_variance / r_vol, 1e-6) : math.max(osc_variance, 1e-6)
            
            float xp0 = x0 + x1
            float xp1 = x1
            float xp2 = x2
            
            float pp00 = p00 + p10 + p01 + p11 + q0
            float pp01 = p01 + p11
            float pp02 = p02 + p12
            float pp10 = p10 + p11
            float pp11 = p11 + q1
            float pp12 = p12
            float pp20 = p20 + p21
            float pp21 = p21
            float pp22 = p22 + q2
            
            float y0 = val - xp0
            float y1 = osc_val - xp2
            
            float S00 = pp00 + R0
            float S01 = pp02
            float S10 = pp20
            float S11 = pp22 + R1
            
            float det_S = (S00 * S11) - (S01 * S10)
            if det_S == 0
                det_S := 1e-12
                
            float invS00 = S11 / det_S
            float invS01 = -S01 / det_S
            float invS10 = -S10 / det_S
            float invS11 = S00 / det_S
            
            float k00 = pp00 * invS00 + pp02 * invS10
            float k01 = pp00 * invS01 + pp02 * invS11
            float k10 = pp10 * invS00 + pp12 * invS10
            float k11 = pp10 * invS01 + pp12 * invS11
            float k20 = pp20 * invS00 + pp22 * invS10
            float k21 = pp20 * invS01 + pp22 * invS11
            
            x0 := xp0 + k00 * y0 + k01 * y1
            x1 := xp1 + k10 * y0 + k11 * y1
            x2 := xp2 + k20 * y0 + k21 * y1
            
            float m00 = 1.0 - k00
            float m01 = 0.0
            float m02 = -k01
            float m10 = -k10
            float m11 = 1.0
            float m12 = -k11
            float m20 = -k20
            float m21 = 0.0
            float m22 = 1.0 - k21
            
            float new_p00 = m00 * pp00 + m02 * pp20
            float new_p01 = m00 * pp01 + m02 * pp21
            float new_p02 = m00 * pp02 + m02 * pp22
            float new_p10 = m10 * pp00 + m11 * pp10 + m12 * pp20
            float new_p11 = m10 * pp01 + m11 * pp11 + m12 * pp21
            float new_p12 = m10 * pp02 + m11 * pp12 + m12 * pp22
            float new_p20 = m20 * pp00 + m22 * pp20
            float new_p21 = m20 * pp01 + m22 * pp21
            float new_p22 = m20 * pp02 + m22 * pp22
            
            p00 := new_p00
            p01 := new_p01
            p02 := new_p02
            p10 := new_p10
            p11 := new_p11
            p12 := new_p12
            p20 := new_p20
            p21 := new_p21
            p22 := new_p22
            
    [x0, x1, x2, p00 + p11 + p22]

// --- Double Exponential Moving Average (DEMA) ---
f_dema(float src, int len) =>
    float e1 = ta.ema(src, len)
    float e2 = ta.ema(e1, len)
    2.0 * e1 - e2

// --- Sigmoid Transition Helpers ---
f_clamp(float val, float low_val, float high_val) => math.min(math.max(val, low_val), high_val)

f_sigmoid(float t) =>
    float x = -6.0 + 12.0 * f_clamp(t, 0.0, 1.0)
    float sMin = 1.0 / (1.0 + math.exp(6.0))
    float sMax = 1.0 / (1.0 + math.exp(-6.0))
    float sig = 1.0 / (1.0 + math.exp(-x))
    (sig - sMin) / (sMax - sMin)

// --- Market Structure Engine (ITH/ITL) ---
bool ms_isInside = high <= high[1] and low >= low[1]

var float[] ms_highs = array.new_float()
var float[] ms_lows = array.new_float()
var int[] ms_idx = array.new_int()

if not ms_isInside
    array.unshift(ms_highs, high)
    array.unshift(ms_lows, low)
    array.unshift(ms_idx, bar_index)
    if array.size(ms_highs) > 100
        array.pop(ms_highs)
        array.pop(ms_lows)
        array.pop(ms_idx)

var array<Swing> ms_sths = array.new<Swing>()
var array<Swing> ms_stls = array.new<Swing>()

bool ms_newSTH = false
bool ms_newSTL = false

var float recent_sth = na
var float recent_stl = na

if not ms_isInside and array.size(ms_highs) >= 3
    float h0 = array.get(ms_highs, 0)
    float h1 = array.get(ms_highs, 1)
    float h2 = array.get(ms_highs, 2)
    float l0 = array.get(ms_lows, 0)
    float l1 = array.get(ms_lows, 1)
    float l2 = array.get(ms_lows, 2)
    int idx1 = array.get(ms_idx, 1)
    
    if h1 >= h0 and h1 > h2
        Swing sth = Swing.new(idx1, h1, true)
        array.push(ms_sths, sth)
        ms_newSTH := true
        recent_sth := h1
        if array.size(ms_sths) > 100
            array.shift(ms_sths)
            
    if l1 <= l0 and l1 < l2
        Swing stl = Swing.new(idx1, l1, false)
        array.push(ms_stls, stl)
        ms_newSTL := true
        recent_stl := l1
        if array.size(ms_stls) > 100
            array.shift(ms_stls)

var array<Swing> ms_itls = array.new<Swing>()
var array<Swing> ms_iths = array.new<Swing>()

var float recent_itl = na
var float recent_ith = na

if ms_newSTL and array.size(ms_stls) >= 3
    int n = array.size(ms_stls)
    Swing s0 = array.get(ms_stls, n - 1)
    Swing s1 = array.get(ms_stls, n - 2)
    Swing s2 = array.get(ms_stls, n - 3)
    if s1.price <= s0.price and s1.price < s2.price
        array.push(ms_itls, s1)
        recent_itl := s1.price
        if array.size(ms_itls) > 50
            array.shift(ms_itls)

if ms_newSTH and array.size(ms_sths) >= 3
    int n = array.size(ms_sths)
    Swing s0 = array.get(ms_sths, n - 1)
    Swing s1 = array.get(ms_sths, n - 2)
    Swing s2 = array.get(ms_sths, n - 3)
    if s1.price >= s0.price and s1.price > s2.price
        array.push(ms_iths, s1)
        recent_ith := s1.price
        if array.size(ms_iths) > 50
            array.shift(ms_iths)

// Calculate real-time market noise for the Kalman Filter on Price
float rolling_variance = ta.variance(close, kf_noise_len)
float system_variance_scaled = rolling_variance * math.pow(10.0, (kf_speed - 50) / 10.0)

float rVol = volume / ta.median(volume, 20)
float clamped_rVol = math.max(0.1, math.min(nz(rVol, 1.0), 3.0))

// Calculate Raw Oscillator for 3D state
float low_n = ta.lowest(low, osc_len)
float high_n = ta.highest(high, osc_len)
float raw_osc = (high_n - low_n) == 0 ? 0.5 : (close - low_n) / (high_n - low_n)
float osc_var = ta.variance(raw_osc, kf_noise_len)

// Compute dynamic baseline via 2D or 3D Kalman Filter
float k_level = na
float k_velocity = na
float k_osc = na
float k_ptrace = na

if use_3d_osc
    [l, v, o, p] = f_kalman3d(close, raw_osc, rolling_variance, osc_var, system_variance_scaled, clamped_rVol, use_vol_weighting)
    k_level := l
    k_velocity := v
    k_osc := o
    k_ptrace := p
else
    [l, v, p] = f_kalman2d(close, rolling_variance, system_variance_scaled, clamped_rVol, use_vol_weighting)
    k_level := l
    k_velocity := v
    k_ptrace := p

// Use native Kalman level and velocity directly
float k_smooth = k_level
float k_slope = k_velocity

float k_error = close - k_smooth // Error calculated from smooth baseline

// Compute dynamic Standard Deviation using Kalman error (EMA decay prevents window drop-off)
float k_variance_ema = ta.ema(k_error * k_error, kf_noise_len)
float k_std = math.sqrt(k_variance_ema) // Residual Variance (For Plunger Exhaustion)
float atr_std = ta.ema(ta.tr, kf_noise_len) // True Range Variance (For Trailing Stop)

// --- Kalman Adaptive Trailing Stop ---
float k_upper = k_smooth + (stop_mult * atr_std)
float k_lower = k_smooth - (stop_mult * atr_std)

var float trailing_stop = na
var int trend_dir = 1
var int active_trade_dir = 0

var float rolling_high = na
var float rolling_low = na

var bool isAdjusting = false
var int sigCounter = 0
var float startLevel = na

if not na(k_upper) and not na(k_lower)
    if na(trailing_stop)
        trailing_stop := close > k_smooth ? k_lower : k_upper
        trend_dir := close > k_smooth ? 1 : -1
        rolling_high := high
        rolling_low := low
    else
        // Uptrend
        if trend_dir == 1
            rolling_high := math.max(nz(rolling_high, high), high)
            bool slope_cond = not use_slope_confirm or k_slope < 0
            
            // Hysteresis breach check: close below stop AND close lower than previous close AND slope confirmed
            float bearish_flip_threshold = trailing_stop - (mu_offset * atr_std)
            if close < bearish_flip_threshold and close < nz(close[1], close) and slope_cond
                trend_dir := -1
                rolling_low := low
                float initial_ceiling = rolling_low + (stop_mult * atr_std)
                trailing_stop := math.min(k_upper, initial_ceiling)
                isAdjusting := false
            else
                // 1. Base Ratchet & Push (Calculate target stop before relaxation)
                float target_stop = math.max(nz(trailing_stop[1]), k_lower)
                
                if k_slope > 0 and close > nz(close[1], close)
                    target_stop := target_stop + (push_factor * atr_std)
                    target_stop := math.min(target_stop, k_upper)
                
                // 1.5 3D Oscillator Tightening (KF4)
                if use_3d_osc and not na(k_osc) and k_osc > 0.9
                    target_stop := math.max(target_stop, close - (0.5 * atr_std))
                
                // 2. Responsive Ratchet
                float structural_floor = na(recent_itl) ? na : (recent_itl - (1.0 * atr_std))
                float live_floor = rolling_high - (stop_mult * atr_std)
                float effective_floor = na(structural_floor) ? live_floor : math.max(structural_floor, live_floor)
                
                if effective_floor > target_stop
                    target_stop := math.min(effective_floor, close - (0.5 * atr_std))
                    
                // 3. Apply Volatility Relaxation ONLY if it expands the stop away from price
                if atr_std > nz(atr_std[1])
                    float relaxation = (atr_std - nz(atr_std[1])) * stop_mult * relax_factor
                    float relaxed_stop = nz(trailing_stop[1]) - relaxation
                    target_stop := math.min(target_stop, math.max(relaxed_stop, k_lower))
                    
                // --- Structural Sigmoid Fusion Layer ---
                if use_sigmoid_smoothing
                    float dist = math.abs(target_stop - nz(trailing_stop[1]))
                    if dist > (0.2 * atr_std) and not isAdjusting
                        isAdjusting := true
                        sigCounter := 0
                        startLevel := nz(trailing_stop[1])
                        
                    if isAdjusting
                        sigCounter += 1
                        float t = sigCounter / math.max(1.0, float(sig_len))
                        float candidate = startLevel + (target_stop - startLevel) * f_sigmoid(t)
                        trailing_stop := math.max(k_lower, candidate)
                        
                        if sigCounter >= sig_len
                            isAdjusting := false
                            trailing_stop := target_stop
                    else
                        trailing_stop := target_stop
                else
                    trailing_stop := target_stop

        // Downtrend
        else if trend_dir == -1
            rolling_low := math.min(nz(rolling_low, low), low)
            bool slope_cond = not use_slope_confirm or k_slope > 0
            
            // Hysteresis breach check: close above stop AND close higher than previous close AND slope confirmed
            float bullish_flip_threshold = trailing_stop + (mu_offset * atr_std)
            if close > bullish_flip_threshold and close > nz(close[1], close) and slope_cond
                trend_dir := 1
                rolling_high := high
                float initial_floor = rolling_high - (stop_mult * atr_std)
                trailing_stop := math.max(k_lower, initial_floor)
                isAdjusting := false
            else
                // 1. Base Ratchet & Push (Calculate target stop before relaxation)
                float target_stop = math.min(nz(trailing_stop[1]), k_upper)
                
                if k_slope < 0 and close < nz(close[1], close)
                    target_stop := target_stop - (push_factor * atr_std)
                    target_stop := math.max(target_stop, k_lower)
                    
                // 1.5 3D Oscillator Tightening (KF4)
                if use_3d_osc and not na(k_osc) and k_osc < 0.1
                    target_stop := math.min(target_stop, close + (0.5 * atr_std))
                    
                // 2. Responsive Ratchet
                float structural_ceiling = na(recent_ith) ? na : (recent_ith + (1.0 * atr_std))
                float live_ceiling = rolling_low + (stop_mult * atr_std)
                float effective_ceiling = na(structural_ceiling) ? live_ceiling : math.min(structural_ceiling, live_ceiling)
                
                if effective_ceiling < target_stop
                    target_stop := math.max(effective_ceiling, close + (0.5 * atr_std))
                    
                // 3. Apply Volatility Relaxation ONLY if it expands the stop away from price
                if atr_std > nz(atr_std[1])
                    float relaxation = (atr_std - nz(atr_std[1])) * stop_mult * relax_factor
                    float relaxed_stop = nz(trailing_stop[1]) + relaxation
                    target_stop := math.max(target_stop, math.min(relaxed_stop, k_upper))
                    
                // --- Structural Sigmoid Fusion Layer ---
                if use_sigmoid_smoothing
                    float dist = math.abs(target_stop - nz(trailing_stop[1]))
                    if dist > (0.2 * atr_std) and not isAdjusting
                        isAdjusting := true
                        sigCounter := 0
                        startLevel := nz(trailing_stop[1])
                        
                    if isAdjusting
                        sigCounter += 1
                        float t = sigCounter / math.max(1.0, float(sig_len))
                        float candidate = startLevel + (target_stop - startLevel) * f_sigmoid(t)
                        trailing_stop := math.min(k_upper, candidate)
                        
                        if sigCounter >= sig_len
                            isAdjusting := false
                            trailing_stop := target_stop
                    else
                        trailing_stop := target_stop
                else
                    trailing_stop := target_stop


var float active_entry_price = na

// --- Regime Filter State Machine ---

bool force_exit = false
if active_trade_dir == 1 and use_tp and high >= active_entry_price + (tp_mult * atr_std)
    force_exit := true
else if active_trade_dir == -1 and use_tp and low <= active_entry_price - (tp_mult * atr_std)
    force_exit := true

if not inSession and active_trade_dir != 0
    force_exit := true

if ta.change(trend_dir) != 0 or force_exit
    if active_trade_dir != 0 and (trend_dir != active_trade_dir or force_exit)
        active_trade_dir := 0

if active_trade_dir == 0
    if trend_dir == 1
        active_trade_dir := 1
    else if trend_dir == -1
        active_trade_dir := -1

// Plot Trailing Stop
color stop_color = active_trade_dir == 1 ? #00998786 : (active_trade_dir == -1 ? #cc33117d : color.new(color.gray, 60))
p_stop = plot(trailing_stop, title="Kalman Trailing Stop", color=stop_color, linewidth=2, style=plot.style_linebr)

step  = (close - trailing_stop) / 6
grad1 = trailing_stop + step * 1
grad2 = trailing_stop + step * 2

p_grad1 = plot(grad1, display = display.none, editable = false)
p_grad2 = plot(grad2, display = display.none, editable = false)

fill(p_stop, p_grad1, color = color.new(stop_color, 68))
fill(p_grad1, p_grad2, color = color.new(stop_color, 88))

// --- S-Plunger Calculations (Cycle-Proportional + Kalman Sweep) ---

// 1. Check for New N-bar High/Low (Structural Trap)
isNewNBarHigh = high == ta.highest(high, plungerLength)
isNewNBarLow  = low  == ta.lowest(low, plungerLength)

// 2. Calculate Bar Range
float barRange = high - low
bool isValidBarRange = barRange > 0

// 3. Check Close Position within Bar Range (Wick Rejection)
bool isCloseInTopPercent = (high - close) / barRange <= closePercentage
bool isCloseInBottomPercent = (close - low) / barRange <= closePercentage

// 4. Volatility Filter (Avoid flat markets)
float averageRange = ta.atr(atrLength)
bool isBarRangeSignificant = na(averageRange) ? false : barRange >= (averageRange / 2)

// 5. Plunger Fat-Tail Kurtosis Tracker (Student-t MLE Approx)
float return_val = nz(ta.change(close), 0.0)
float mean_ret = ta.ema(return_val, kf_noise_len)
float diff_ret = return_val - mean_ret
float var2 = ta.ema(diff_ret * diff_ret, kf_noise_len)
float var4 = ta.ema(math.pow(diff_ret, 4), kf_noise_len)
float kurtosis = (var2 > 0) ? (var4 / (var2 * var2)) : 3.0
// Clamp kurtosis to prevent extreme tail distortion (assume 3.0 is normal Gaussian)
float clamped_kurtosis = math.max(3.0, math.min(kurtosis, 20.0))

// Dynamically warp the sweep multiplier for Fat-Tail Exhaustion
float dynamic_sweep_mult = base_sweep_mult * math.sqrt(clamped_kurtosis / 3.0)

bool isBullishSweep = low < (k_smooth - dynamic_sweep_mult * k_std)
bool isBearishSweep = high > (k_smooth + dynamic_sweep_mult * k_std)

// --- Signal Conditions (Upgraded) ---
bullishPlungerCondition = isNewNBarLow and close > open and isCloseInTopPercent and isValidBarRange and isBarRangeSignificant and isBullishSweep
bearishPlungerCondition = isNewNBarHigh and close < open and isCloseInBottomPercent and isValidBarRange and isBarRangeSignificant and isBearishSweep

// --- Plotting Signals ---
plotshape(series = showBullish and bullishPlungerCondition ? low : na, title = 'Bullish Signal', location = location.belowbar, color = #00998784, style = shape.triangleup, size = size.tiny, display = display.all - display.status_line)
barcolor(bullishPlungerCondition ? #009988 : na, title = 'Bullish Color')

plotshape(series = showBearish and bearishPlungerCondition ? high : na, title = 'Bearish Signal', location = location.abovebar, color = #cc33117e, style = shape.triangledown, size = size.tiny, display = display.all - display.status_line)
barcolor(bearishPlungerCondition ? #CC3311 : na, title = 'Bearish Color')

// --- Main Trend Entry Signals ---
bool entered_long = active_trade_dir != nz(active_trade_dir[1]) and active_trade_dir == 1
bool entered_short = active_trade_dir != nz(active_trade_dir[1]) and active_trade_dir == -1
bool exited_to_cash = active_trade_dir != nz(active_trade_dir[1]) and active_trade_dir == 0

float atr_14 = ta.atr(14)
plotshape(series = entered_long ? low - atr_14 : na, title = 'Main Entry Long', location = location.absolute, color = #009988, style = shape.diamond, size = size.tiny, display = display.all - display.status_line)
plotshape(series = entered_short ? high + atr_14 : na, title = 'Main Entry Short', location = location.absolute, color = #CC3311, style = shape.diamond, size = size.tiny, display = display.all - display.status_line)

// --- Risk % Label ---
float sl_distance = math.abs(close - trailing_stop)
float sl_percent = sl_distance / close
float risk_amount = account_size * risk_percent
float dollar_risk_per_unit = sl_distance * syminfo.pointvalue
float raw_position_size = dollar_risk_per_unit > 0 ? (risk_amount / dollar_risk_per_unit) : 0.0

// Apply Account Buying Power Cap
float max_affordable_size = (account_size * max_leverage) / (close * syminfo.pointvalue)
float position_size = math.min(raw_position_size, max_affordable_size)
float pyramid_size = position_size * pyramid_factor

plot(position_size, title="Position Size", display=display.none)
plot(pyramid_size, title="Pyramid Size", display=display.none)



// --- Abnormal Reaction Warning (Livermore Ejector Concept) ---
float avg_tr = ta.ema(ta.tr, 20)
bool abnormal_bear_warning = trend_dir == 1 and close < open and ta.tr > (avg_tr * 2.5)
bool abnormal_bull_warning = trend_dir == -1 and close > open and ta.tr > (avg_tr * 2.5)

plotshape(series=abnormal_bear_warning ? high : na, title="Abnormal Bear Warning", location=location.abovebar, color=color.new(color.yellow, 30), style=shape.xcross, size=size.tiny, display = display.all - display.status_line)
plotshape(series=abnormal_bull_warning ? low : na, title="Abnormal Bull Warning", location=location.belowbar, color=color.new(color.yellow, 30), style=shape.xcross, size=size.tiny, display = display.all - display.status_line)

// --- Algorithmic Pyramiding (Scale-In) ---
var int add_cooldown = 0
var array<int> live_trade_times = array.new_int()

// --- Performance Tracking (Profit Factor) ---
var float gross_profit_all = 0.0
var float gross_loss_all = 0.0
var int total_trades_all = 0
var array<float> recent_profits = array.new_float()
var array<float> recent_losses = array.new_float()

if active_trade_dir != nz(active_trade_dir[1]) and nz(active_trade_dir[1]) != 0
    total_trades_all += 1
    float exit_price = close
    float trade_return = (exit_price - active_entry_price) / active_entry_price
    if nz(active_trade_dir[1]) == -1
        trade_return := -trade_return
        
    if trade_return > 0
        gross_profit_all += trade_return
        array.push(recent_profits, trade_return)
        array.push(recent_losses, 0.0)
    else
        gross_loss_all += math.abs(trade_return)
        array.push(recent_profits, 0.0)
        array.push(recent_losses, math.abs(trade_return))
        
    if array.size(recent_profits) > 50
        array.shift(recent_profits)
        array.shift(recent_losses)

// --- Update Entry Price for New Trades ---
if active_trade_dir != nz(active_trade_dir[1]) and active_trade_dir != 0
    active_entry_price := close
    array.unshift(live_trade_times, time)
    if array.size(live_trade_times) > 500
        array.pop(live_trade_times)

add_cooldown := math.max(0, nz(add_cooldown[1]) - 1)

bool can_pyramid_long = (active_trade_dir == 1) and (trailing_stop >= active_entry_price)
bool can_pyramid_short = (active_trade_dir == -1) and (trailing_stop <= active_entry_price)

var float pullback_sum_vol_long = 0.0
var int pullback_count_long = 0
var float pullback_sum_vol_short = 0.0
var int pullback_count_short = 0

if ms_newSTH
    pullback_sum_vol_long := 0.0
    pullback_count_long := 0
if ms_newSTL
    pullback_sum_vol_short := 0.0
    pullback_count_short := 0

pullback_sum_vol_long += volume
pullback_count_long += 1
pullback_sum_vol_short += volume
pullback_count_short += 1

float pullback_vol_long = pullback_count_long > 0 ? (pullback_sum_vol_long / pullback_count_long) : nz(volume)
float pullback_vol_short = pullback_count_short > 0 ? (pullback_sum_vol_short / pullback_count_short) : nz(volume)
float macro_vol_avg = ta.median(volume, 20)

// Exclude the current breakout candle's volume from the dry-up check
bool volume_dried_up_long = nz(pullback_vol_long[1], volume) < (macro_vol_avg * 0.9)
bool volume_dried_up_short = nz(pullback_vol_short[1], volume) < (macro_vol_avg * 0.9)

// Confirm the breakout candle has expanding volume
bool breakout_vol_confirm_long = volume > nz(pullback_vol_long[1], volume)
bool breakout_vol_confirm_short = volume > nz(pullback_vol_short[1], volume)

bool pyramid_long_trigger = ta.crossover(close, recent_sth) and volume_dried_up_long and breakout_vol_confirm_long and can_pyramid_long and add_cooldown == 0
bool pyramid_short_trigger = ta.crossunder(close, recent_stl) and volume_dried_up_short and breakout_vol_confirm_short and can_pyramid_short and add_cooldown == 0

if pyramid_long_trigger or pyramid_short_trigger
    add_cooldown := 5

plotshape(series=pyramid_long_trigger ? low : na, title="Pyramid Long (ADD)", location=location.belowbar, color=color.new(#009988, 30), style=shape.cross, size=size.tiny, display = display.all - display.status_line)
plotshape(series=pyramid_short_trigger ? high : na, title="Pyramid Short (ADD)", location=location.abovebar, color=color.new(#CC3311, 30), style=shape.cross, size=size.tiny, display = display.all - display.status_line)

// --- Alerts ---
alertcondition(bullishPlungerCondition, title = 'Bullish Plunger', message = 'Bullish Plunger Detected (Statistically Validated)')
alertcondition(bearishPlungerCondition, title = 'Bearish Plunger', message = 'Bearish Plunger Detected (Statistically Validated)')

// --- Trade Alerts (Regime Filtered) ---
alertcondition(entered_long, title = 'Trade Entry: LONG', message = 'Trend flipped Bullish. ENTER LONG.')
alertcondition(entered_short, title = 'Trade Entry: SHORT', message = 'Trend flipped Bearish. ENTER SHORT.')
alertcondition(exited_to_cash, title = 'Trade Exit: CASH', message = 'Trailing Stop Hit. EXIT POSITION.')

alertcondition(abnormal_bear_warning or abnormal_bull_warning, title='Abnormal Reaction Warning', message='Abnormal Market Reaction Warning (Volatility Spike Against Trend)')
alertcondition(pyramid_long_trigger, title='Pyramid Long (ADD)', message='Algorithmic Scale-In Long Detected (Volume Dry-Up + Breakout)')
alertcondition(pyramid_short_trigger, title='Pyramid Short (ADD)', message='Algorithmic Scale-In Short Detected (Volume Dry-Up + Breakout)')

// --- Williams Market Structure Visualization ---
var polyline iLine = na
var array<label> iLabels = array.new<label>()
// OPT: Track last known swing array sizes to skip redundant redraws
var int last_iths_size = 0
var int last_itls_size = 0
int curr_iths_size = array.size(ms_iths)
int curr_itls_size = array.size(ms_itls)
bool swings_changed = (curr_iths_size != last_iths_size) or (curr_itls_size != last_itls_size)

if show_williams_ms and (swings_changed or barstate.islast)
    last_iths_size := curr_iths_size
    last_itls_size := curr_itls_size
    if not na(iLine)
        polyline.delete(iLine)
    if array.size(iLabels) > 0
        for i = 0 to array.size(iLabels) - 1
            label.delete(array.get(iLabels, i))
    array.clear(iLabels)

    if array.size(ms_itls) > 0 or array.size(ms_iths) > 0
        array<Swing> all_iSwings = array.new<Swing>()
        
        if array.size(ms_itls) > 0
            for i = 0 to array.size(ms_itls) - 1
                array.push(all_iSwings, array.get(ms_itls, i))
                
        if array.size(ms_iths) > 0
            for i = 0 to array.size(ms_iths) - 1
                array.push(all_iSwings, array.get(ms_iths, i))
            
        array<int> sort_times = array.new<int>()
        
        if array.size(all_iSwings) > 0
            for i = 0 to array.size(all_iSwings) - 1
                array.push(sort_times, array.get(all_iSwings, i).idx)
                
            array<int> sorted_indices = array.sort_indices(sort_times)
            
            array<Swing> clean_iSwings = array.new<Swing>()
            
            bool last_was_high = false
            
            if array.size(sorted_indices) > 0
                for i = 0 to sorted_indices.size() - 1
                    Swing current = array.get(all_iSwings, array.get(sorted_indices, i))
                    
                    if array.size(clean_iSwings) == 0
                        array.push(clean_iSwings, current)
                        last_was_high := current.isHigh
                    else
                        if current.isHigh == last_was_high
                            Swing last_swing = array.get(clean_iSwings, array.size(clean_iSwings) - 1)
                            if current.isHigh and current.price > last_swing.price
                                array.set(clean_iSwings, array.size(clean_iSwings) - 1, current)
                            else if not current.isHigh and current.price < last_swing.price
                                array.set(clean_iSwings, array.size(clean_iSwings) - 1, current)
                        else
                            Swing last_swing = array.get(clean_iSwings, array.size(clean_iSwings) - 1)
                            if (current.isHigh and current.price > last_swing.price) or (not current.isHigh and current.price < last_swing.price)
                                array.push(clean_iSwings, current)
                                last_was_high := current.isHigh

            array<chart.point> iPoints = array.new<chart.point>()
            
            if array.size(clean_iSwings) > 0
                for i = 0 to array.size(clean_iSwings) - 1
                    Swing s = array.get(clean_iSwings, i)
                    array.push(iPoints, chart.point.from_index(s.idx, s.price))
                    
                    string txt = s.isHigh ? "ITH" : "ITL"
                    color col = s.isHigh ? color.new(color.red, 30) : color.new(color.green, 30)
                    lbl = label.new(s.idx, s.price, text=txt, style=s.isHigh ? label.style_label_down : label.style_label_up, color=color.new(color.white, 100), textcolor=col, size=size.tiny, text_font_family = font.family_monospace)
                    array.push(iLabels, lbl)
                    
                if array.size(iPoints) > 1
                    iLine := polyline.new(iPoints, curved=false, line_color=color.new(color.blue, 30), line_width=1, line_style=line.style_dotted)


// --- Performance Table ---
var table perf_table = na
if barstate.islast
    float recent_profit_sum = array.sum(recent_profits)
    float recent_loss_sum = array.sum(recent_losses)
    float global_pf = gross_loss_all > 0 ? (gross_profit_all / gross_loss_all) : (gross_profit_all > 0 ? 99.99 : 0.0)
    float recent_pf = recent_loss_sum > 0 ? (recent_profit_sum / recent_loss_sum) : (recent_profit_sum > 0 ? 99.99 : 0.0)
    
    if na(perf_table)
        perf_table := table.new(position.middle_right, 2, 3, frame_color=color.white, frame_width=1, border_color=color.white, border_width=1)
        
    table.cell(perf_table, 0, 0, "Global PF (" + str.tostring(total_trades_all) + ")", text_color=color.white, bgcolor=color.black, text_size=size.tiny, text_font_family=font.family_monospace)
    table.cell(perf_table, 1, 0, str.tostring(global_pf, "#.##"), text_color=color.white, bgcolor=global_pf > 1.2 ? color.new(#009988, 50) : color.new(#CC3311, 50), text_size=size.tiny, text_font_family=font.family_monospace)
    
    table.cell(perf_table, 0, 1, "Recent PF (" + str.tostring(array.size(recent_profits)) + ")", text_color=color.white, bgcolor=color.black, text_size=size.tiny, text_font_family=font.family_monospace)
    table.cell(perf_table, 1, 1, str.tostring(recent_pf, "#.##"), text_color=color.white, bgcolor=recent_pf > 1.2 ? color.new(#009988, 50) : color.new(#CC3311, 50), text_size=size.tiny, text_font_family=font.family_monospace)
    
    table.cell(perf_table, 0, 2, "Trailing Stop", text_color=color.white, bgcolor=color.black, text_size=size.tiny, text_font_family=font.family_monospace)
    table.cell(perf_table, 1, 2, str.tostring(trailing_stop, format.mintick) + " (" + str.tostring(sl_percent * 100, "#.##") + "%)", text_color=color.white, bgcolor=trend_dir == 1 ? color.new(#009988, 70) : color.new(#CC3311, 70), text_size=size.tiny, text_font_family=font.family_monospace)

Overview

A wick-and-sweep filter marks liquidity-sweep signals relative to the band. Optional features include a sigmoid-smoothed stop, a 3D oscillator state, ATR take-profit, session filter and position sizing inputs from account size and risk percent. Market-structure points can be shown. Many settings are exposed and the signals are off by default.

Details

Published by

vedkanani123 on GitHub

Code credit

fdx101, as written in the script's header

Type

Indicator, Pine Script v6

Draws

On the price chart

Category

Trend, Volatility & bands, SMC / ICT / price action

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

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

SettingTypeDefaultVariable
Dominant Cycle Periodint28dominantCyclePeriod
Wick Filter %float30closePercentage
Statistical Sweep Multiplier (SD)float1.5base_sweep_mult
Kalman Responsiveness (1-100)int50kf_speed
Volume-Weighted Variancebooltrueuse_vol_weighting
Noise Variance Windowint100kf_noise_len
Kalman Smoothing (DEMA)int5kf_smooth_len
Use Kalman Slope Confirmationbooltrueuse_slope_confirm
Signal Offset (μ in ATRs)float0.0mu_offset
Trend Agreement Push (ATR %)float0.2push_factor
Use Sigmoid Smoothingbooltrueuse_sigmoid_smoothing
Sigmoid Transition Lengthint1sig_len
Show all 28 settings
SettingTypeDefaultVariable
Trailing Stop Distance (SD)float3.0stop_mult
Volatility Relaxation %float0.1relax_factor
Enable 3D Oscillator State (KF4)boolfalseuse_3d_osc
Oscillator Lookback Lengthint14osc_len
Use Take Profitbooltrueuse_tp
Take Profit Multiplier (ATR)float4.0tp_mult
Account Size ($)float10000account_size
Risk Per Trade (%)float2.0risk_percent
Pyramid Size Multiplierfloat0.5pyramid_factor
Max Account Leveragefloat1.0max_leverage
Show Bullish SignalsboolfalseshowBullish
Show Bearish SignalsboolfalseshowBearish
Show Market Structure (ITH/ITL)boolfalseshow_williams_ms
Enable Intraday FilterboolfalseuseIntraday
Trading Session Windowsession0945-1550sessionStr
Session TimezonestringAmerica/New_YorksessionTz
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

This indicator detects market accumulation phases and bottom formations over time rather than generating single-point buy signals. It evaluates five scoring components: trend loss, range formation, volatility compression, liquidity behavior, and early bullish

Indicator v6 TrendVolatility & bands
Updated 2 days ago · MIT GitHub repository of Vein: Accumulation Phase 4H [WavesUnchained]

This technical indicator combines multiple popular analysis tools into a single price chart overlay to optimize chart space. It calculates and plots the complete Ichimoku Cloud, 50 and 200 Simple Moving Averages, and standard Bollinger Bands. Furthermore, it t

Indicator v5 TrendMoving averages Repaints
Updated 3 years ago · None stated GitHub repository of Ichimoku + MAs + Bollinger + Liquidity Zone

Imagegpt Test 1

by g-moe
3 stars

Imagegpt Test 1 is a comprehensive chart overlay indicator that plots buy, sell, and reversal signals along with supply and demand zones. It calculates key levels using volume pivots and features integrated Bollinger Bands alongside entry, exit, stop-loss, and

Indicator v5 Support & resistanceSMC / ICT / price action
Updated 5 months ago · None stated GitHub repository of Imagegpt Test 1

Algo1.0

by dtheran1
1 stars

A combined overlay with two modules for the price chart. The ATR Trend module draws a trend line from an ATR-based band, with ten choices of smoothing type, an optional fast trend, a smoothed range channel and buy and sell markers when the trend flips. The sec

Indicator v6 TrendSMC / ICT / price action Repaints
Updated 6 months ago · None stated GitHub repository of Algo1.0

Mac Buy/Sell Indicator

by acquillemac
1 stars

A SuperTrend-style indicator built on ATR and RMA bands that prints buy and sell signals when the direction flips, optionally filtered by a 9 and 21 EMA cross. It draws two ATR-based take profit levels, background colour and a stats table that scores past sign

Indicator v6 TrendSMC / ICT / price action
Updated 6 weeks ago · None stated GitHub repository of Mac Buy/Sell Indicator

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers