// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// CVD Divergence engine © TFlab | Trailing-stop engine © ZenAndTheArtOfTrading / PineScriptMastery.com
// Strategy conversion: CVD divergences drive entries; TSL/ATR logic drives stops & exits.
//@version=5
strategy("Cumulative Volume Delta Divergence + ATR Trailing Stop", shorttitle="CVD Div Strategy", overlay=false,
max_bars_back = 5000, max_labels_count = 500, max_lines_count = 500,
pyramiding = 0, initial_capital = 10000,
default_qty_type = strategy.percent_of_equity, default_qty_value = 10,
commission_type = strategy.commission.percent, commission_value = 0)
n = input.int(1, title ="Divergence Fractal Periods", minval=1 , group = 'Logic Setting')
Period = input.int(21 , 'CVD Period', group = 'Logic Setting')
CumMode = input.string('Periodic','Cumulative Mode', [ 'Periodic' , 'EMA'], group = 'Logic Setting')
Show_Label = input.string('No' , 'Show Label' , options = ['Yes' , 'No'], group = 'display Setting')
// ───────────────────────────────────────────────────────────────────────────────────────────────
// STRATEGY INPUTS
// ───────────────────────────────────────────────────────────────────────────────────────────────
grpDir = 'Strategy — Direction'
grpFilter = 'Strategy — Divergence Strength Filter'
grpTrail = 'Strategy — Trailing Stop (TSL / ATR)'
grpTP = 'Strategy — Take Profit (Reward : Risk)'
grpER = 'Regime Filter — Efficiency Ratio (ER)'
tradeDirection = input.string('Long & Short', 'Trade Direction', options = ['Long & Short', 'Long only', 'Short only'], group = grpDir,
tooltip = 'A confirmed BULLISH divergence opens a long; a confirmed BEARISH divergence opens a short. Use this to restrict to one side.')
// Strength = how many times the divergence has CONSECUTIVELY re-confirmed against a fresh pivot before
// price reverses it. Tick the strengths you want to trade.
strengthTip = "Divergence 'strength' = how many times the same divergence has repeated in a row (each repeat is a new pivot confirming the same divergence) before it gets cancelled.\n\n• Normal = 1st occurrence — a single, fresh divergence (count = 1).\n• Good = 2nd consecutive occurrence — the divergence has now formed twice in a row (count = 2).\n• Strong = 3rd+ consecutive occurrence — a persistent, stacked divergence (count ≥ 3).\n\nAn entry is allowed only if the signal's strength matches one of the ticked boxes. Untick all of one side to skip the weakest setups."
useNormal = input.bool(true, 'Normal (count = 1)', group = grpFilter, tooltip = strengthTip)
useGood = input.bool(true, 'Good (count = 2)', group = grpFilter)
useStrong = input.bool(false, 'Strong (count ≥ 3)', group = grpFilter)
// Trailing stop engine (ported from "TSL ATR" by ZenAndTheArtOfTrading)
exit_type = input.string('Wick', 'Exit Type', options = ['Close', 'Wick'], group = grpTrail,
tooltip = 'Wick = the trail is a hard stop order, so an intrabar wick through it exits. Close = only exit once a bar CLOSES beyond the trail.')
trailSource = input.string('Lows/Highs', 'Trail Source', options = ['Lows/Highs', 'Close', 'Open'], group = grpTrail)
trailMethod = input.string('ATR', 'Trail Method', options = ['ATR', 'Percent'], group = grpTrail)
trailPercent = input.float(10, 'Trail Percent', minval = 0.1, group = grpTrail)
swingLookback = input.int(7, 'Swing Lookback', group = grpTrail)
atrPeriod = input.int(14, 'ATR Period', group = grpTrail)
atrMultiplier = input.float(0.5, 'ATR Multiplier', step = 0.5, group = grpTrail)
// Take profit at a fixed Reward:Risk multiple of the INITIAL stop distance (risk locked at entry).
useTP = input.bool(true, 'Enable Take Profit', group = grpTP,
tooltip = 'Places a fixed take-profit at a multiple of the INITIAL stop-loss distance (the risk taken at entry). Because the stop trails, the TP is locked at entry and stays put for the life of the trade.')
rrRatio = input.float(0.5, 'Reward : Risk', minval = 0.1, step = 0.1, group = grpTP,
tooltip = 'TP distance = this × the initial stop distance. e.g. 2 → the take-profit is twice as far from entry as the stop (a 1 : 2 risk/reward).')
// Efficiency Ratio regime filter (ported from "Efficiency Ratio (Market Noise)" by Alexter6277).
// The ER colour cascade classifies each bar into one of four regimes by |ER|:
// Purple = Extreme (≥ Extreme%) · Green = High (≥ High%) · Blue = Low/Med (≥ Low%) · Red = Noise (< Low%)
useERFilter = input.bool(true, 'Enable ER Regime Filter', group = grpER,
tooltip = 'When on, an entry is only taken if the current bar\'s ER regime colour is one of the colours ticked below. When off, ER is ignored.')
ERLength = input.int(10, 'ER Length', minval = 1, group = grpER)
UseDirectional = input.bool(false, 'Use Directional ER', group = grpER,
tooltip = 'If true, ER keeps the sign of the move (range −100..100); if false the absolute ratio (0..100) is used. The colour buckets use |ER| either way.')
ExtremeLevel = input.float(95.0, '% Extreme Level (Purple)', minval = 0, maxval = 100, group = grpER)
HighLevel = input.float(50.0, '% High Level (Green)', minval = 0, maxval = 100, group = grpER)
LowLevel = input.float(25.0, '% Low Level (Blue)', minval = 0, maxval = 100, group = grpER)
erAllowPurple = input.bool(false, 'Allow Purple — Extreme (|ER| ≥ Extreme%)', group = grpER, tooltip = 'Strongest / most efficient trend.')
erAllowGreen = input.bool(false, 'Allow Green — High', group = grpER)
erAllowBlue = input.bool(true, 'Allow Blue — Low/Medium', group = grpER)
erAllowRed = input.bool(true, 'Allow Red — Noise (|ER| < Low%)', group = grpER, tooltip = 'Choppiest / least efficient (most market noise).')
showERbg = input.bool(false, 'Shade chart by ER regime', group = grpER,
tooltip = 'Tints the price-chart background with the current ER colour so you can see which regime each bar is in.')
Buying = volume * ((close - low) / (high - low))
Selling = volume * ((high - close) / (high - low))
delta = Buying - Selling
Hist = if CumMode == "Periodic"
math.sum(delta , Period)
else if CumMode == "EMA"
ta.ema(delta , Period)
//Trend/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
EMA = ta.ema(close, 50)
up_trend =close[n] > EMA
down_trend = close[n] < EMA
//Fractal////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// UpFractal
UpPivot = ta.pivothigh(n,n)
// downFractal
downPivot = ta.pivotlow(n,n)
upFractal = UpPivot and up_trend
downFractal = downPivot and down_trend
// plotshape(downFractal and Show_Fractal =='Yes', style=shape.triangleup, location=location.belowbar, offset=-n, color= color.rgb(57, 160, 60), size = size.tiny)
// plotshape(upFractal and Show_Fractal =='Yes', style=shape.triangledown, location=location.abovebar, offset=-n, color= color.rgb(236, 69, 69), size = size.tiny)
//Data//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Bearish Divergennce Data
//price Data
High_Last_Price = ta.valuewhen(upFractal,high[n],0)
High_Per_Price = ta.valuewhen(upFractal,high[n],1)
//MACD hist Data
High_Last_Hist = ta.valuewhen(upFractal, Hist[n],0)
High_Per_Hist = ta.valuewhen(upFractal, Hist[n],1)
//Bar index Data
High_Last_Bar = ta.valuewhen(upFractal, bar_index[n],0)
High_Per_Bar = ta.valuewhen(upFractal, bar_index[n],1)
// Time Condition Bullish
Time_Condition_Bear = (High_Last_Bar + 30) > bar_index
//Bearish Divergenc Detector////////////////////////
Last_Bearish_Divergece = if High_Last_Hist > 0 and High_Per_Hist > 0 and Time_Condition_Bear and (High_Last_Bar - High_Per_Bar) < 30
(High_Last_Price > High_Per_Price) and (High_Last_Hist < High_Per_Hist)
else
false
//count_Bear_Divergence
var int CBearDive = 0
if ta.change(High_Last_Price) and Last_Bearish_Divergece == true
CBearDive := CBearDive + 1
else if Last_Bearish_Divergece == false
CBearDive := 0
//Detecte change phase in Bearish Divergence
var bool Bear_Phase = na
len_back_for_Bear_Phase_check = if not na(High_Last_Bar - High_Per_Bar)
High_Last_Bar - High_Per_Bar
else
1
if ta.change(High_Last_Price) and Last_Bearish_Divergece == true
for i = (n+1) to (n+1) + len_back_for_Bear_Phase_check
if Hist[i] < 0
Bear_Phase := true
else if Last_Bearish_Divergece == false
Bear_Phase := false
// Bullish Divergennce Data
//price Data
Low_Last_Price = ta.valuewhen(downFractal,low[n],0)
Low_Per_Price = ta.valuewhen(downFractal,low[n],1)
//MACD hist Data
Low_Last_Hist = ta.valuewhen(downFractal, Hist[n],0)
Low_Per_Hist = ta.valuewhen(downFractal, Hist[n],1)
//Bar index Data
Low_Last_Bar = ta.valuewhen(downFractal, bar_index[n],0)
Low_Per_Bar = ta.valuewhen(downFractal, bar_index[n],1)
// Time Condition Bullish
Time_Condition_Bull = (Low_Last_Bar + 30) > bar_index
//Bullish Divergenc Detector////////////////////////
Last_Bullish_Divergece = if Low_Last_Hist < 0 and Low_Per_Hist < 0 and Time_Condition_Bull and (Low_Last_Bar - Low_Per_Bar) < 30
(Low_Last_Price < Low_Per_Price) and (Low_Last_Hist > Low_Per_Hist)
else
false
//count_Bear_Divergence
var int CBullDive = 0
if ta.change(Low_Last_Price) and Last_Bullish_Divergece == true
CBullDive := CBullDive + 1
else if Last_Bullish_Divergece == false
CBullDive := 0
//Detecte and count change phase in Bullish Divergence
//var int CBullPhase = 0
var bool Bull_Phase = na
len_back_for_Bull_Phase_check = if not na(Low_Last_Bar - Low_Per_Bar)
Low_Last_Bar - Low_Per_Bar
else
1
if ta.change(Low_Last_Price) and Last_Bullish_Divergece == true
for i = (n+1) to (n+1) + len_back_for_Bull_Phase_check
if Hist[i] > 0
//CBullPhase := CBullPhase + 1
Bull_Phase := true
else if Last_Bullish_Divergece == false
Bull_Phase := false
//Score Divergence
text_power_bull = if CBullDive == 1
"Normal Bull Dive"
else if CBullDive == 2
"Good Bull Dive"
else if CBullDive >= 3
"Strong Bull Dive"
else
"-"
text_power_bear = if CBearDive == 1
"Normal Bear Dive"
else if CBearDive == 2
"Good Bear Dive"
else if CBearDive >= 3
"Strong Bear Dive"
else
"-"
// ───────────────────────────────────────────────────────────────────────────────────────────────
// ENTRY SIGNALS — derived from the CVD divergence engine above
// ───────────────────────────────────────────────────────────────────────────────────────────────
// A "signal" fires on the bar a brand-new divergence is confirmed against a fresh pivot — i.e. the
// same edge on which CBullDive / CBearDive increments. At that moment the counter already holds the
// new strength, so we read it directly for the Normal/Good/Strong filter.
bullStrengthOK = (CBullDive == 1 and useNormal) or (CBullDive == 2 and useGood) or (CBullDive >= 3 and useStrong)
bearStrengthOK = (CBearDive == 1 and useNormal) or (CBearDive == 2 and useGood) or (CBearDive >= 3 and useStrong)
bullSignal = ta.change(Low_Last_Price) and Last_Bullish_Divergece and bullStrengthOK
bearSignal = ta.change(High_Last_Price) and Last_Bearish_Divergece and bearStrengthOK
longAllowed = tradeDirection == 'Long & Short' or tradeDirection == 'Long only'
shortAllowed = tradeDirection == 'Long & Short' or tradeDirection == 'Short only'
// ── Efficiency Ratio regime filter ──────────────────────────────────────────
EfficiencyRatio(source, length, directional) =>
netchange = directional ? source - source[length] : math.abs(source - source[length])
SumOfChanges = 0.0
for i = 0 to length - 1
SumOfChanges := SumOfChanges + math.abs(source[i] - source[i + 1])
(netchange / SumOfChanges) * 100
ERval = EfficiencyRatio(close, ERLength, UseDirectional)
// Classify the current bar into exactly one regime (same cascade as the original ER indicator)
erIsPurple = (ERval >= ExtremeLevel or ERval <= -ExtremeLevel)
erIsGreen = not erIsPurple and (ERval >= HighLevel or ERval <= -HighLevel)
erIsBlue = not erIsPurple and not erIsGreen and (ERval >= LowLevel or ERval <= -LowLevel)
erIsRed = not erIsPurple and not erIsGreen and not erIsBlue
erColor = erIsPurple ? color.purple : erIsGreen ? color.green : erIsBlue ? color.blue : color.red
// Entry is allowed when the filter is off, or the current regime is one of the ticked colours
erRegimeOK = (erIsPurple and erAllowPurple) or (erIsGreen and erAllowGreen) or (erIsBlue and erAllowBlue) or (erIsRed and erAllowRed)
erFilterOK = not useERFilter or erRegimeOK
// Optional: tint the price chart with the current ER regime colour for visual checking
bgcolor(showERbg ? color.new(erColor, 85) : na, force_overlay = true)
// Trailing-stop-only design: only open a trade when flat, so an opposite divergence never reverses
// the position — the trailing stop is the sole exit. The ER regime filter further gates entries.
if strategy.position_size == 0 and erFilterOK
if bullSignal and not bearSignal and longAllowed
strategy.entry('Long', strategy.long)
else if bearSignal and not bullSignal and shortAllowed
strategy.entry('Short', strategy.short)
// ───────────────────────────────────────────────────────────────────────────────────────────────
// TRAILING STOP — ported from the "TSL ATR" indicator/strategy
// ───────────────────────────────────────────────────────────────────────────────────────────────
var float trailPrice = na // current trail stop level
var float tpPrice = na // fixed take-profit level (locked at entry)
float next_trailPrice = na // candidate trail level for this bar
// Required trailing-stop variables
atrValue = ta.atr(atrPeriod) * atrMultiplier
float swingLow = ta.lowest(low, swingLookback)
float swingHigh = ta.highest(high, swingLookback)
// Candidate trail price for the current bar / direction
if trailMethod == "ATR" // ATR-based trail
next_trailPrice := switch trailSource
"Close" => strategy.position_size > 0 ? close - atrValue : close + atrValue
"Open" => strategy.position_size > 0 ? open - atrValue : open + atrValue
=> strategy.position_size > 0 ? swingLow - atrValue : swingHigh + atrValue
else if trailMethod == "Percent" // Percentage-based trail
float percentMulti = strategy.position_size > 0 ? (100 - trailPercent) / 100 : (100 + trailPercent) / 100
next_trailPrice := switch trailSource
"Close" => close * percentMulti
"Open" => open * percentMulti
=> strategy.position_size > 0 ? swingLow * percentMulti : swingHigh * percentMulti
// Detect the first bar of a brand-new position
isNewTrade = strategy.position_size != 0 and strategy.position_size[1] == 0
if isNewTrade
// Anchor the INITIAL stop a full atrValue away from the ACTUAL fill price
// (strategy.position_avg_price). This guarantees the ATR distance is measured from where
// you actually entered — not from a swing/close/open level that can sit inside the entry
// bar and stop the trade out on the very bar it opens. If the chosen trail source is even
// further away (a wider, safer stop), keep that instead.
float entryStop = strategy.position_size > 0 ? strategy.position_avg_price - atrValue : strategy.position_avg_price + atrValue
trailPrice := na(next_trailPrice) ? entryStop : (strategy.position_size > 0 ? math.min(entryStop, next_trailPrice) : math.max(entryStop, next_trailPrice))
// Lock the take-profit from the INITIAL stop distance (risk) so the R:R is fixed for the trade
float initialRisk = math.abs(strategy.position_avg_price - trailPrice)
tpPrice := strategy.position_size > 0 ? strategy.position_avg_price + initialRisk * rrRatio : strategy.position_avg_price - initialRisk * rrRatio
// On later bars, ratchet the trail in the favorable direction only
else if strategy.position_size != 0 and barstate.isconfirmed
// Trail long stop ONLY IF the candidate is higher
if strategy.position_size > 0 and next_trailPrice > trailPrice
trailPrice := next_trailPrice
alert(message = "Trailing Stop updated for " + syminfo.tickerid + ": " + str.tostring(trailPrice, "#.#####"), freq = alert.freq_once_per_bar_close)
// Trail short stop ONLY IF the candidate is lower
if strategy.position_size < 0 and next_trailPrice < trailPrice
trailPrice := next_trailPrice
alert(message = "Trailing Stop updated for " + syminfo.tickerid + ": " + str.tostring(trailPrice, "#.#####"), freq = alert.freq_once_per_bar_close)
// Flat: clear the stop / take-profit so nothing leaks into the next trade
if strategy.position_size == 0
trailPrice := na
tpPrice := na
// Draw the trailing stop (red) and the take-profit (green) onto the PRICE chart, for the life of the trade
plot(strategy.position_size != 0 ? trailPrice : na, color = color.red, title = "Trailing Stop", style = plot.style_linebr, force_overlay = true)
plot(useTP and strategy.position_size != 0 ? tpPrice : na, color = color.lime, title = "Take Profit", style = plot.style_linebr, linewidth = 1, force_overlay = true)
// Apply the exit. The trailing stop and the take-profit form one bracket: whichever is reached
// first closes the trade (and the line for that level stops being plotted on the next bar).
float exitLimit = useTP ? tpPrice : na // take-profit (touch) for Wick mode
if exit_type == "Wick"
// Stop hit by an intrabar wick exits; TP fills as a touch limit. Skip if neither level is set.
// comment_loss labels the stop fill "SL", comment_profit labels the take-profit fill "TP".
if not na(trailPrice) or not na(exitLimit)
strategy.exit(id = "Long Exit", from_entry = "Long", stop = trailPrice, limit = exitLimit, comment_loss = "SL", comment_profit = "TP")
strategy.exit(id = "Short Exit", from_entry = "Short", stop = trailPrice, limit = exitLimit, comment_loss = "SL", comment_profit = "TP")
else if exit_type == "Close"
// Stop and TP both require a confirmed close beyond their level
if strategy.position_size > 0 and not na(trailPrice) and close < trailPrice
strategy.close("Long", comment = "SL")
if strategy.position_size < 0 and not na(trailPrice) and close > trailPrice
strategy.close("Short", comment = "SL")
if useTP and strategy.position_size > 0 and close > tpPrice
strategy.close("Long", comment = "TP")
if useTP and strategy.position_size < 0 and close < tpPrice
strategy.close("Short", comment = "TP")
// ───────────────────────────────────────────────────────────────────────────────────────────────
// CVD divergence visuals (unchanged from the original indicator)
// ───────────────────────────────────────────────────────────────────────────────────────────────
//Divergence Line & Label
Drawing()=>
var line BeLine = na
var line BuLine = na
var label BeLabel = na
var label BuLabel = na
var line BeLine_OnChart = na
var line BuLine_OnChart = na
var label BeLabel_OnChart = na
var label BuLabel_OnChart = na
//
if Last_Bullish_Divergece
BuLine := line.new(Low_Per_Bar, Low_Per_Hist, Low_Last_Bar,Low_Last_Hist , color = color.rgb(21, 151, 25) , width = 2)
if Show_Label == 'Yes'
BuLabel := label.new(Low_Last_Bar,Low_Last_Hist , '+RD', color = color.green, textcolor = color.white, size = size.tiny , style = label.style_label_up)
BuLine_OnChart := line.new(Low_Per_Bar, low[bar_index - Low_Per_Bar], Low_Last_Bar,low[bar_index - Low_Last_Bar] , color = color.rgb(21, 151, 25) , width = 2 , force_overlay = true)
if Show_Label == 'Yes'
BuLabel_OnChart := label.new(Low_Last_Bar,low[bar_index - Low_Last_Bar] , '+RD', color = color.green, textcolor = color.white, size = size.tiny , style = label.style_label_up, force_overlay = true)
if Last_Bearish_Divergece
BeLine := line.new(High_Per_Bar,High_Per_Hist, High_Last_Bar,High_Last_Hist , color = color.rgb(212, 44, 44), width = 2)
if Show_Label == 'Yes'
BeLabel := label.new(High_Last_Bar,High_Last_Hist ,'-RD', color = color.red, textcolor = color.white, size = size.tiny, style = label.style_label_down)
BeLine_OnChart := line.new(High_Per_Bar,high[bar_index - High_Per_Bar], High_Last_Bar,high[bar_index - High_Last_Bar] , color = color.rgb(212, 44, 44), width = 2, force_overlay = true)
if Show_Label == 'Yes'
BeLabel_OnChart := label.new(High_Last_Bar,high[bar_index - High_Last_Bar] ,'-RD', color = color.red, textcolor = color.white, size = size.tiny, style = label.style_label_down, force_overlay = true)
Hist_Color = if Hist > 0
color.rgb(76, 175, 79, 50)
else if Hist < 0
color.rgb(255, 82, 82, 50)
plot( Hist , 'Show Cumulative Vol Delta' ,color = Hist_Color , style = plot.style_columns)
Drawing()
Show all 382 lines