EMA Channel Reversal.pine
Copy
// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © claygrace74
// Technical spec: 5-EMA Reversal Strategy
// Visual spec: EMA Channel Reversal Design Document
//@version=6
strategy("EMA Channel Reversal", shorttitle="EMA Ch Rev", overlay=true, initial_capital=1000, default_qty_type=strategy.percent_of_equity, default_qty_value=10, max_lines_count=500, max_boxes_count=500, max_labels_count=500)
// ==================================================================================
// PHASE 1: INPUTS & CONFIGURATION
// ==================================================================================
grp_ma = "Moving Averages"
len_long = input.int(200, "Long Term EMA Length", group=grp_ma)
len_fast = input.int(5, "Fast Momentum EMA (White)", group=grp_ma)
len_slow = input.int(8, "Slow Momentum EMA (Green)", group=grp_ma)
grp_strat = "Strategy Rules"
use_alt_entry = input.bool(false, "Use Alternative Entry (Price Action)", group=grp_strat)
rr_ratio = input.float(2.0, "Risk/Reward Ratio", minval=0.5, step=0.5, group=grp_strat)
grp_vis = "Visual Design"
i_showHUD = input.bool(true, "Show Status HUD", group=grp_vis)
i_showRR = input.bool(true, "Show R:R Overlay", group=grp_vis)
i_showGlow = input.bool(true, "Show Neon Glow on Momentum Lines", group=grp_vis)
i_showPullback = input.bool(true, "Show Pullback Diamonds", group=grp_vis)
// ==================================================================================
// COLOR PALETTE (from Visual Design Spec)
// ==================================================================================
color C_SLATE = #708090 // 200 EMA Channel
color C_WHITE = #FFFFFF // Fast EMA (5)
color C_MINT = #00FFAB // Slow EMA (8)
color C_GOLD = #FFD700 // Pullback warning
color C_SIG_GREEN = #39FF14 // Bullish entry / TP
color C_SIG_RED = #FF3131 // Bearish entry / SL
// ==================================================================================
// PHASE 2: INDICATOR CALCULATIONS
// ==================================================================================
ema200_high = ta.ema(high, len_long)
ema200_close = ta.ema(close, len_long)
ema200_low = ta.ema(low, len_long)
ema_fast = ta.ema(close, len_fast)
ema_slow = ta.ema(close, len_slow)
// ==================================================================================
// PHASE 3: PLOTTING & VISUALIZATION
// ==================================================================================
// --- 200 EMA Channel ---
p_high = plot(ema200_high, color=color.new(C_SLATE, 30), linewidth=1, title="EMA 200 High")
p_low = plot(ema200_low, color=color.new(C_SLATE, 30), linewidth=1, title="EMA 200 Low")
plot(ema200_close, color=color.new(C_SLATE, 50), linewidth=1, style=plot.style_line, title="EMA 200 Close")
// --- Momentum Ribbon with Neon Glow ---
plot(i_showGlow ? ema_fast : na, "Fast Glow", color=color.new(C_WHITE, 70), linewidth=5)
plot(ema_fast, "EMA 5 (Fast)", color=C_WHITE, linewidth=2)
plot(i_showGlow ? ema_slow : na, "Slow Glow", color=color.new(C_MINT, 70), linewidth=5)
plot(ema_slow, "EMA 8 (Slow)", color=C_MINT, linewidth=2)
// ==================================================================================
// PHASE 4: STATE MANAGEMENT (CRITICAL LOGIC)
// Combined breakout + momentum confirmation into single state check.
// Uses var persistence to track the sequence: Confirm -> Pullback -> Entry.
// ==================================================================================
// State Tracking Variables
var int trend_state = 0 // 1 = Bullish, -1 = Bearish, 0 = Neutral
var bool pullback_active = false
var float swing_high = na // Tracked for Price Action entry mode
var float swing_low = na
// 1. Initial Signal & Confirmation (Phase 1+2 combined)
// Both price AND momentum EMAs must be outside the channel
price_above_channel = close > ema200_high
emas_above_channel = ema_fast > ema200_high and ema_slow > ema200_high
price_below_channel = close < ema200_low
emas_below_channel = ema_fast < ema200_low and ema_slow < ema200_low
// Set or update trend state
if price_above_channel and emas_above_channel
trend_state := 1
if not pullback_active
swing_high := high
pullback_active := false
else if price_below_channel and emas_below_channel
trend_state := -1
if not pullback_active
swing_low := low
pullback_active := false
// Update swing extremes while waiting for pullback
if trend_state == 1 and not pullback_active
swing_high := math.max(nz(swing_high, high), high)
if trend_state == -1 and not pullback_active
swing_low := math.min(nz(swing_low, low), low)
// 2. Pullback Detection
// Bullish pullback: price low touches or enters the channel from above
if trend_state == 1 and low <= ema200_high
pullback_active := true
// Bearish pullback: price high touches or enters the channel from below
if trend_state == -1 and high >= ema200_low
pullback_active := true
// Dynamic channel fill — changes color with bias
chanFillColor = trend_state == 1 ? color.new(C_SIG_GREEN, 90) : trend_state == -1 ? color.new(C_SIG_RED, 90) : color.new(C_SLATE, 90)
fill(p_high, p_low, color=chanFillColor, title="EMA 200 Channel")
// ==================================================================================
// PHASE 5: ENTRY TRIGGERS
// ==================================================================================
bool long_entry = false
bool short_entry = false
if use_alt_entry
// Alternative: Breakout of swing high/low after pullback
if trend_state == 1 and pullback_active and close > swing_high
long_entry := true
if trend_state == -1 and pullback_active and close < swing_low
short_entry := true
else
// Standard: EMA crossover after pullback
if trend_state == 1 and pullback_active and ta.crossover(ema_fast, ema_slow)
long_entry := true
if trend_state == -1 and pullback_active and ta.crossunder(ema_fast, ema_slow)
short_entry := true
// ==================================================================================
// PHASE 6: EXECUTION & RISK MANAGEMENT
// ==================================================================================
var float sl_level = na
var float tp_level = na
if long_entry and strategy.position_size == 0
sl_level := ema200_low
float risk = close - sl_level
tp_level := close + (risk * rr_ratio)
strategy.entry("Long", strategy.long)
// Reset state to prevent re-entry on same setup
trend_state := 0
pullback_active := false
if short_entry and strategy.position_size == 0
sl_level := ema200_high
float risk = sl_level - close
tp_level := close - (risk * rr_ratio)
strategy.entry("Short", strategy.short)
trend_state := 0
pullback_active := false
// Exit orders
if strategy.position_size > 0
strategy.exit("Exit Long", "Long", stop=sl_level, limit=tp_level)
if strategy.position_size < 0
strategy.exit("Exit Short", "Short", stop=sl_level, limit=tp_level)
// ==================================================================================
// SIGNAL SHAPES
// ==================================================================================
// Pullback diamond (gold, inside channel)
bool newPullback = pullback_active and not pullback_active[1]
plotshape(i_showPullback and newPullback, style=shape.diamond, location=location.absolute, color=C_GOLD, size=size.small, title="Pullback Diamond")
// Entry arrows
plotshape(long_entry and strategy.position_size == 0, style=shape.triangleup, location=location.belowbar, color=C_SIG_GREEN, text="LONG", textcolor=C_SIG_GREEN, size=size.small, title="Long Entry")
plotshape(short_entry and strategy.position_size == 0, style=shape.triangledown, location=location.abovebar, color=C_SIG_RED, text="SHORT", textcolor=C_SIG_RED, size=size.small, title="Short Entry")
// ==================================================================================
// R:R OVERLAY (Visual Design Spec Section 5)
// ==================================================================================
var line slLine = na
var line tpLine = na
var line entLine = na
var box profitBox = na
var box lossBox = na
var label slLabel = na
var label tpLabel = na
// Clean previous frame
line.delete(slLine)
line.delete(tpLine)
line.delete(entLine)
box.delete(profitBox)
box.delete(lossBox)
label.delete(slLabel)
label.delete(tpLabel)
if i_showRR and strategy.position_size != 0 and not na(sl_level) and not na(tp_level)
float ep = strategy.position_avg_price
int startBar = bar_index - 1
int endBar = bar_index + 20
// Entry line
entLine := line.new(startBar, ep, endBar, ep, color=color.new(color.white, 50), width=1, style=line.style_dotted)
// SL line
slLine := line.new(startBar, sl_level, endBar, sl_level, color=C_SIG_RED, width=1, style=line.style_solid)
// TP line
tpLine := line.new(startBar, tp_level, endBar, tp_level, color=C_SIG_GREEN, width=1, style=line.style_solid)
// Profit/loss zone boxes
if strategy.position_size > 0
profitBox := box.new(startBar, tp_level, endBar, ep, bgcolor=color.new(C_SIG_GREEN, 85), border_color=color(na))
lossBox := box.new(startBar, ep, endBar, sl_level, bgcolor=color.new(C_SIG_RED, 85), border_color=color(na))
else
profitBox := box.new(startBar, ep, endBar, tp_level, bgcolor=color.new(C_SIG_GREEN, 85), border_color=color(na))
lossBox := box.new(startBar, sl_level, endBar, ep, bgcolor=color.new(C_SIG_RED, 85), border_color=color(na))
// Labels
slLabel := label.new(endBar + 2, sl_level, "SL", style=label.style_none, textcolor=C_SIG_RED, size=size.small)
tpLabel := label.new(endBar + 2, tp_level, "TP", style=label.style_none, textcolor=C_SIG_GREEN, size=size.small)
// ==================================================================================
// STATUS HUD (Visual Design Spec Section 6)
// ==================================================================================
var table hud = table.new(position.top_right, 2, 5, bgcolor=color.new(#000000, 60), border_width=1, border_color=color.new(color.gray, 60))
if i_showHUD and barstate.islast
// Bias
string biasText = trend_state == 1 ? "BULLISH" : trend_state == -1 ? "BEARISH" : "NEUTRAL"
color biasColor = trend_state == 1 ? C_SIG_GREEN : trend_state == -1 ? C_SIG_RED : color.gray
table.cell(hud, 0, 0, "BIAS", text_color=color.gray, text_size=size.small)
table.cell(hud, 1, 0, biasText, text_color=biasColor, text_size=size.small)
// Momentum alignment
bool aligned = (trend_state == 1 and emas_above_channel) or (trend_state == -1 and emas_below_channel)
table.cell(hud, 0, 1, "MOMENTUM", text_color=color.gray, text_size=size.small)
table.cell(hud, 1, 1, aligned ? "ALIGNED" : "DISALIGNED", text_color=aligned ? C_SIG_GREEN : color.new(C_SIG_RED, 30), text_size=size.small)
// Current step
string stepText = trend_state == 0 ? "IDLE" : pullback_active ? "3. Pullback" : (trend_state == 1 and emas_above_channel) or (trend_state == -1 and emas_below_channel) ? "2. Confirmed" : "1. Breakout"
color stepClr = pullback_active ? C_GOLD : trend_state != 0 ? color.white : color.gray
table.cell(hud, 0, 2, "STEP", text_color=color.gray, text_size=size.small)
table.cell(hud, 1, 2, stepText, text_color=stepClr, text_size=size.small)
// Position status
string posText = strategy.position_size > 0 ? "LONG" : strategy.position_size < 0 ? "SHORT" : "FLAT"
color posClr = strategy.position_size > 0 ? C_SIG_GREEN : strategy.position_size < 0 ? C_SIG_RED : color.gray
table.cell(hud, 0, 3, "POSITION", text_color=color.gray, text_size=size.small)
table.cell(hud, 1, 3, posText, text_color=posClr, text_size=size.small)
// Entry mode
table.cell(hud, 0, 4, "MODE", text_color=color.gray, text_size=size.small)
table.cell(hud, 1, 4, use_alt_entry ? "Price Action" : "Crossover", text_color=color.white, text_size=size.small)
// ==================================================================================
// ALERT CONDITIONS
// ==================================================================================
alertcondition(trend_state == 1 and trend_state[1] != 1, "Bullish Bias Confirmed", "Price and EMAs confirmed above 200 EMA channel")
alertcondition(trend_state == -1 and trend_state[1] != -1, "Bearish Bias Confirmed", "Price and EMAs confirmed below 200 EMA channel")
alertcondition(newPullback and trend_state == 1, "Bullish Pullback Active", "Price pulled back to 200 EMA channel — watch for long entry")
alertcondition(newPullback and trend_state == -1, "Bearish Pullback Active", "Price pulled back to 200 EMA channel — watch for short entry")
alertcondition(long_entry and strategy.position_size == 0, "Long Entry Triggered", "All conditions met — long entry executed")
alertcondition(short_entry and strategy.position_size == 0, "Short Entry Triggered", "All conditions met — short entry executed")
Show all 261 lines