Стратегия двустороннего импульса банковского индекса

KERNEL VWAP CVD RSI ATR
Дата создания: 2025-10-29 15:57:59 Последнее изменение: 2025-10-29 15:57:59
Копировать: 0 Количество просмотров: 151
2
Подписаться
319
Подписчики

Стратегия двустороннего импульса банковского индекса Стратегия двустороннего импульса банковского индекса

Что это за тактика?

Представьте себе, что у вас есть старый водитель, который умеет управлять автомобилем и двигаться в обратном направлении, и он уверенно держится в ногу с рынком, независимо от того, в какую сторону он идет!

Портфель ключевых технологических показателей

В этой стратегии используется комбинация из пяти сверхмощных показателей:

  • Возвращение к ядру“Все, что мы делаем, - мы делаем для того, чтобы показать, что мы хотим, чтобы это произошло”.
  • VWAP“Объем сделок, взвешенный в среднем по цене, говорит о том, где находится “большой капитал”
  • Накопленный дефицит КВД“Открытие истинного противоположности между покупательской и продажной силой”
  • Относительно слабый RSIНапример, в Китае, в Китае, в Китае, в Китае, в Китае.
  • ATR реальный диапазон колебанийДвижущаяся остановка убытков, адаптация к рыночным колебаниям

Механизм многократного подтверждения

Самый умный аспект этой стратегии - это “многократная проверка”, которая требует, как при покупке жилья, отслеживания площади, типа жилья и цены:

  • Тенденционное согласование ((двойная подтверждение Kernel + VWAP)
  • Совместное сбытовое количество (или увеличение или непрерывное увеличение)
  • K-линейный формат перехода (≥ 60% от общего количества объектов, чтобы избежать ловушки крестозвезды)
  • Ограничение временного окна (только в 9:15-15:15, чтобы избежать путаницы в открытии и закрытии диска)

️ Интеллектуальное управление рисками

Эта стратегия по управлению рисками, как в учебниках!

  • Динамическая остановкаATR, фиксированный балл, колебание высоких и низких точек.
  • Разделение отходовПервая цель - ликвидировать 50% позиций, чтобы прибыль бежала
  • Отслеживание остановки“После получения прибыли автоматически переносится остановка убытков и блокируется прибыль”.
  • Ежедневные ограниченияНапример, в Китае, где добыча нефти и газа достигает уровня 3,5% в год, в Китае добыча нефти и газа достигает уровня 3,5%.
  • Управление деньгамиРиск по каждой сделке - менее 1%

Рекомендации по применению в боевых условиях

Если вы хотите использовать эту стратегию, помните о следующем:

  1. Лучший циклОптимистичный график в 5 минут позволяет зафиксировать кратковременные колебания, но не слишком часто.
  2. Подходящие сортаФьючерсы, предназначенные для банковских индексов, но могут использоваться и другие индексы
  3. Время сделкиИндийское время: 9:15-15:15, избегайте шума на двух K-линиях передней части диска
  4. Контроль положенияРекомендуется от 25 до 75 контрактов, но не более 200 контрактов.

Самая большая особенность этой стратегии заключается в том, что “вы можете атаковать, а вы можете защитить”, что бык может сделать больше, медведь может сделать меньше, а рынок не боится колебаний!

Исходный код стратегии
/*backtest
start: 2025-01-01 00:00:00
end: 2025-10-28 00:00:00
period: 10m
basePeriod: 10m
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
*/

//@version=5
strategy("BankNifty LONG & SHORT Multi-Confluence", 
     shorttitle="BN Long-Short",
     overlay=true)

// ═══════════════════════════════════════════════════════════
// 📊 STRATEGY INFO
// ═══════════════════════════════════════════════════════════
// Timeframe: 5-MINUTE CHART (Recommended)
// Direction: LONG & SHORT (Both directions)
// Asset: BankNifty Futures
// Style: Intraday Momentum Trading
// ═══════════════════════════════════════════════════════════

// ═══════════════════════════════════════════════════════════
// ⚙️ INPUT PARAMETERS
// ═══════════════════════════════════════════════════════════

// Trading Direction
tradingDirection = input.string("Both", "Trading Direction", 
     options=["Long Only", "Short Only", "Both"], group="🎯 Strategy Type")

// Trading Hours (IST)
startHour = input.int(9, "Start Hour", minval=0, maxval=23, group="⏰ Trading Session")
startMinute = input.int(15, "Start Minute", minval=0, maxval=59, group="⏰ Trading Session")
endHour = input.int(15, "End Hour", minval=0, maxval=23, group="⏰ Trading Session")
endMinute = input.int(15, "End Minute", minval=0, maxval=59, group="⏰ Trading Session")
avoidFirstCandles = input.int(2, "Skip First N Candles", minval=0, maxval=10, group="⏰ Trading Session")

// Position Sizing
lotSize = input.int(25, "Lot Size", minval=15, maxval=75, group="💰 Position Management")
maxContracts = input.int(75, "Max Contracts", minval=25, maxval=200, step=25, group="💰 Position Management")
riskPercent = input.float(1.0, "Risk % Per Trade", minval=0.5, maxval=3.0, step=0.1, group="💰 Position Management")

// Kernel Regression
kernelLength = input.int(20, "Kernel Length", minval=10, maxval=50, group="📈 Indicators")

// VWAP
vwapSource = input.string("Session", "VWAP Type", options=["Session", "Rolling"], group="📈 Indicators")
vwapRollingLength = input.int(20, "Rolling VWAP Length", minval=10, maxval=100, group="📈 Indicators")

// Volume
volumeLength = input.int(20, "Volume MA Length", minval=10, maxval=50, group="📊 Volume")
volumeMultiplier = input.float(1.5, "Volume Spike", minval=1.1, maxval=3.0, step=0.1, group="📊 Volume")

// CVD
useCVD = input.bool(true, "Use CVD Filter", group="📊 Volume")
cvdLength = input.int(10, "CVD Smoothing", minval=5, maxval=30, group="📊 Volume")

// RSI
useRSI = input.bool(true, "Use RSI Filter", group="🎯 Filters")
rsiLength = input.int(14, "RSI Length", minval=7, maxval=30, group="🎯 Filters")
rsiOverbought = input.int(70, "RSI Overbought", minval=60, maxval=85, group="🎯 Filters")
rsiOversold = input.int(30, "RSI Oversold", minval=15, maxval=40, group="🎯 Filters")

// Price Action
candleBodyPercent = input.float(0.6, "Min Body %", minval=0.4, maxval=0.9, step=0.05, group="🎯 Filters")
minCandlePoints = input.float(20.0, "Min Candle Size", minval=10.0, maxval=100.0, step=5.0, group="🎯 Filters")

// Stop Loss
stopLossType = input.string("ATR", "Stop Loss Type", options=["Fixed", "ATR", "Swing"], group="🛡️ Exits")
atrLength = input.int(14, "ATR Length", minval=7, maxval=30, group="🛡️ Exits")
atrMultiplier = input.float(1.5, "ATR Multiplier", minval=0.5, maxval=3.0, step=0.1, group="🛡️ Exits")
fixedStopLoss = input.float(50.0, "Fixed SL (points)", minval=20.0, maxval=200.0, step=10.0, group="🛡️ Exits")
swingLookback = input.int(10, "Swing Lookback", minval=5, maxval=30, group="🛡️ Exits")

// Targets
rrTarget1 = input.float(1.5, "Target 1 R:R", minval=0.5, maxval=3.0, step=0.1, group="🛡️ Exits")
rrTarget2 = input.float(2.5, "Target 2 R:R", minval=1.0, maxval=5.0, step=0.5, group="🛡️ Exits")
partialExitPercent = input.int(50, "Partial Exit %", minval=25, maxval=75, step=5, group="🛡️ Exits")

// Trailing
useTrailing = input.bool(true, "Trailing Stop", group="🛡️ Exits")
trailActivationRR = input.float(1.2, "Activate at R:R", minval=0.5, maxval=2.0, step=0.1, group="🛡️ Exits")
trailStopRR = input.float(0.8, "Trail Stop R:R", minval=0.3, maxval=1.5, step=0.1, group="🛡️ Exits")

// Risk Management
maxTradesPerDay = input.int(5, "Max Trades/Day", minval=1, maxval=20, group="🚨 Risk")
maxConsecutiveLosses = input.int(2, "Stop After Losses", minval=1, maxval=5, group="🚨 Risk")
dailyLossLimit = input.float(2.5, "Daily Loss %", minval=1.0, maxval=10.0, step=0.5, group="🚨 Risk")
dailyProfitTarget = input.float(5.0, "Daily Target %", minval=2.0, maxval=20.0, step=0.5, group="🚨 Risk")

// ═══════════════════════════════════════════════════════════
// 📊 INDICATORS
// ═══════════════════════════════════════════════════════════

// Kernel Regression
kernel = ta.linreg(close, kernelLength, 0)
kernelSlope = kernel - kernel[1]

// VWAP
vwapValue = vwapSource == "Session" ? ta.vwap(close) : ta.sma(hlc3, vwapRollingLength)
vwapSlope = vwapValue - vwapValue[1]

// Volume
volumeMA = ta.sma(volume, volumeLength)
highVolume = volume > volumeMA * volumeMultiplier
volumeIncreasing = volume > volume[1] and volume[1] > volume[2]

// CVD
buyVolume = close > open ? volume : 0
sellVolume = close < open ? volume : 0
volumeDelta = buyVolume - sellVolume
cvd = ta.cum(volumeDelta)
cvdMA = ta.sma(cvd, cvdLength)

// RSI
rsi = ta.rsi(close, rsiLength)

// ATR
atr = ta.atr(atrLength)

// Price Action
candleRange = high - low
candleBody = math.abs(close - open)
bodyPercent = candleRange > 0 ? candleBody / candleRange : 0

// Swing High/Low
swingHigh = ta.highest(high, swingLookback)
swingLow = ta.lowest(low, swingLookback)

// ═══════════════════════════════════════════════════════════
// ⏰ TIME MANAGEMENT
// ═══════════════════════════════════════════════════════════

currentMinutes = hour * 60 + minute
startMinutes = startHour * 60 + startMinute
endMinutes = endHour * 60 + endMinute
tradingTime = currentMinutes >= startMinutes and currentMinutes <= endMinutes

var int barsToday = 0
newDay = ta.change(dayofweek)
if newDay
    barsToday := 0
if tradingTime
    barsToday += 1

skipInitialBars = barsToday <= avoidFirstCandles
forceExitTime = currentMinutes >= (endMinutes - 5)

// ═══════════════════════════════════════════════════════════
// 💼 POSITION TRACKING
// ═══════════════════════════════════════════════════════════

var int todayTrades = 0
var int consecutiveLosses = 0
var float todayPnL = 0.0
var float entryPrice = na
var int entryBar = 0
var float stopLoss = na
var float target1 = na
var float target2 = na
var float trailStop = na
var bool target1Hit = false
var bool trailActive = false

// Daily Reset
if newDay
    todayTrades := 0
    consecutiveLosses := 0
    todayPnL := 0.0

// Track P&L
if strategy.closedtrades > 0 and strategy.closedtrades != strategy.closedtrades[1]
    lastPnL = strategy.closedtrades.profit(strategy.closedtrades - 1)
    todayPnL += lastPnL
    if lastPnL < 0
        consecutiveLosses += 1
    else
        consecutiveLosses := 0

// Risk Limits
lossLimit = strategy.initial_capital * (dailyLossLimit / 100)
profitTarget = strategy.initial_capital * (dailyProfitTarget / 100)
hitDailyTarget = todayPnL >= profitTarget
hitLossLimit = math.abs(todayPnL) >= lossLimit

canTrade = todayTrades < maxTradesPerDay and
     consecutiveLosses < maxConsecutiveLosses and
     not hitDailyTarget and
     not hitLossLimit and
     tradingTime and
     not skipInitialBars and
     strategy.position_size == 0

// ═══════════════════════════════════════════════════════════
// 🎯 LONG ENTRY CONDITIONS
// ═══════════════════════════════════════════════════════════

// Bullish Trend
longTrend = kernelSlope > 0 and close > kernel
longVWAP = close > vwapValue and vwapSlope > 0
longCVD = useCVD ? cvd > cvdMA and ta.rising(cvd, 2) : true
longRSI = useRSI ? rsi > rsiOversold and rsi < rsiOverbought : true
longVolume = highVolume or volumeIncreasing
longCandle = close > open and bodyPercent >= candleBodyPercent and 
     candleRange >= minCandlePoints and close >= (high - (candleRange * 0.25))

longSignal = longTrend and longVWAP and longCVD and longRSI and longVolume and longCandle and canTrade and
     (tradingDirection == "Long Only" or tradingDirection == "Both")

// ═══════════════════════════════════════════════════════════
// 🎯 SHORT ENTRY CONDITIONS
// ═══════════════════════════════════════════════════════════

// Bearish Trend
shortTrend = kernelSlope < 0 and close < kernel
shortVWAP = close < vwapValue and vwapSlope < 0
shortCVD = useCVD ? cvd < cvdMA and ta.falling(cvd, 2) : true
shortRSI = useRSI ? rsi < rsiOverbought and rsi > rsiOversold : true
shortVolume = highVolume or volumeIncreasing
shortCandle = close < open and bodyPercent >= candleBodyPercent and 
     candleRange >= minCandlePoints and close <= (low + (candleRange * 0.25))

shortSignal = shortTrend and shortVWAP and shortCVD and shortRSI and shortVolume and shortCandle and canTrade and
     (tradingDirection == "Short Only" or tradingDirection == "Both")

// ═══════════════════════════════════════════════════════════
// 💰 POSITION SIZING
// ═══════════════════════════════════════════════════════════

calcPositionSize(stopDistance) =>
    riskAmount = strategy.initial_capital * (riskPercent / 100)
    contracts = math.floor(riskAmount / stopDistance)
    math.min(contracts, maxContracts)

// ═══════════════════════════════════════════════════════════
// 📥 LONG ENTRY
// ═══════════════════════════════════════════════════════════

if longSignal
    entryPrice := close
    entryBar := bar_index
    
    // Calculate Stop Loss
    if stopLossType == "ATR"
        stopLoss := close - (atr * atrMultiplier)
    else if stopLossType == "Swing"
        stopLoss := math.min(swingLow, close - (atr * atrMultiplier))
    else
        stopLoss := close - fixedStopLoss
    
    risk = entryPrice - stopLoss
    target1 := entryPrice + (risk * rrTarget1)
    target2 := entryPrice + (risk * rrTarget2)
    
    qty = calcPositionSize(risk)
    
    trailStop := stopLoss
    target1Hit := false
    trailActive := false
    todayTrades += 1
    
    strategy.entry("LONG", strategy.long, qty=qty, comment="Long-" + str.tostring(todayTrades))

// ═══════════════════════════════════════════════════════════
// 📥 SHORT ENTRY
// ═══════════════════════════════════════════════════════════

if shortSignal
    entryPrice := close
    entryBar := bar_index
    
    // Calculate Stop Loss
    if stopLossType == "ATR"
        stopLoss := close + (atr * atrMultiplier)
    else if stopLossType == "Swing"
        stopLoss := math.max(swingHigh, close + (atr * atrMultiplier))
    else
        stopLoss := close + fixedStopLoss
    
    risk = stopLoss - entryPrice
    target1 := entryPrice - (risk * rrTarget1)
    target2 := entryPrice - (risk * rrTarget2)
    
    qty = calcPositionSize(risk)
    
    trailStop := stopLoss
    target1Hit := false
    trailActive := false
    todayTrades += 1
    
    strategy.entry("SHORT", strategy.short, qty=qty, comment="Short-" + str.tostring(todayTrades))

// ═══════════════════════════════════════════════════════════
// 🏃 TRAILING STOP (LONG)
// ═══════════════════════════════════════════════════════════

if strategy.position_size > 0 and useTrailing
    unrealizedProfit = close - entryPrice
    risk = entryPrice - stopLoss
    
    if unrealizedProfit >= (risk * trailActivationRR) and not trailActive
        trailActive := true
        trailStop := close - (risk * trailStopRR)
    
    if trailActive
        newTrailStop = close - (risk * trailStopRR)
        if newTrailStop > trailStop
            trailStop := newTrailStop

// ═══════════════════════════════════════════════════════════
// 🏃 TRAILING STOP (SHORT)
// ═══════════════════════════════════════════════════════════

if strategy.position_size < 0 and useTrailing
    unrealizedProfit = entryPrice - close
    risk = stopLoss - entryPrice
    
    if unrealizedProfit >= (risk * trailActivationRR) and not trailActive
        trailActive := true
        trailStop := close + (risk * trailStopRR)
    
    if trailActive
        newTrailStop = close + (risk * trailStopRR)
        if newTrailStop < trailStop
            trailStop := newTrailStop

// ═══════════════════════════════════════════════════════════
// 📤 LONG EXIT
// ═══════════════════════════════════════════════════════════

if strategy.position_size > 0
    activeSL = trailActive and useTrailing ? trailStop : stopLoss
    
    // Stop Loss
    if low <= activeSL
        strategy.close("LONG", comment="Long-SL")
    
    // Target 1
    else if high >= target1 and not target1Hit
        exitQty = math.floor(strategy.position_size * (partialExitPercent / 100))
        strategy.close("LONG", qty=exitQty, comment="Long-T1")
        target1Hit := true
        stopLoss := entryPrice
    
    // Target 2
    else if high >= target2
        strategy.close("LONG", comment="Long-T2")
    
    // Force Exit
    else if forceExitTime
        strategy.close("LONG", comment="Long-EOD")

// ═══════════════════════════════════════════════════════════
// 📤 SHORT EXIT
// ═══════════════════════════════════════════════════════════

if strategy.position_size < 0
    activeSL = trailActive and useTrailing ? trailStop : stopLoss
    
    // Stop Loss
    if high >= activeSL
        strategy.close("SHORT", comment="Short-SL")
    
    // Target 1
    else if low <= target1 and not target1Hit
        exitQty = math.floor(math.abs(strategy.position_size) * (partialExitPercent / 100))
        strategy.close("SHORT", qty=exitQty, comment="Short-T1")
        target1Hit := true
        stopLoss := entryPrice
    
    // Target 2
    else if low <= target2
        strategy.close("SHORT", comment="Short-T2")
    
    // Force Exit
    else if forceExitTime
        strategy.close("SHORT", comment="Short-EOD")

// ═══════════════════════════════════════════════════════════
// ⚠️ REVERSAL EXIT
// ═══════════════════════════════════════════════════════════

longReversal = strategy.position_size > 0 and (close < kernel and kernelSlope < 0 or close < vwapValue)
shortReversal = strategy.position_size < 0 and (close > kernel and kernelSlope > 0 or close > vwapValue)

if longReversal and target1Hit
    strategy.close("LONG", comment="Long-Rev")
if shortReversal and target1Hit
    strategy.close("SHORT", comment="Short-Rev")

// ═══════════════════════════════════════════════════════════
// 🎨 VISUALIZATION
// ═══════════════════════════════════════════════════════════

// Indicators
plot(kernel, "Kernel", color=color.new(color.purple, 0), linewidth=2)
plot(vwapValue, "VWAP", color=color.new(color.orange, 0), linewidth=2)

kernelUpper = kernel + atr
kernelLower = kernel - atr
plot(kernelUpper, "K Upper", color=color.new(color.purple, 70), linewidth=1, style=plot.style_circles)
plot(kernelLower, "K Lower", color=color.new(color.purple, 70), linewidth=1, style=plot.style_circles)

// Signals
plotshape(longSignal, "LONG", shape.triangleup, location.belowbar, 
     color=color.new(color.lime, 0), size=size.normal, text="LONG")
plotshape(shortSignal, "SHORT", shape.triangledown, location.abovebar, 
     color=color.new(color.red, 0), size=size.normal, text="SHORT")

// Position Levels
posColor = strategy.position_size > 0 ? color.blue : strategy.position_size < 0 ? color.orange : na
plot(strategy.position_size != 0 ? entryPrice : na, "Entry", 
     color=color.new(posColor, 0), style=plot.style_linebr, linewidth=2)

slColor = strategy.position_size > 0 ? color.red : color.fuchsia
plot(strategy.position_size != 0 ? stopLoss : na, "SL", 
     color=color.new(slColor, 0), style=plot.style_linebr, linewidth=2)

t1Color = strategy.position_size > 0 ? color.green : color.aqua
plot(strategy.position_size != 0 ? target1 : na, "T1", 
     color=color.new(t1Color, 0), style=plot.style_linebr, linewidth=1)
plot(strategy.position_size != 0 ? target2 : na, "T2", 
     color=color.new(t1Color, 0), style=plot.style_linebr, linewidth=2)

plot(strategy.position_size != 0 and trailActive ? trailStop : na, "Trail", 
     color=color.new(color.yellow, 0), style=plot.style_linebr, linewidth=2)

// Volume
barcolor(highVolume ? color.new(color.blue, 70) : na)

// Background
bgcolor(tradingTime ? color.new(color.green, 98) : color.new(color.gray, 95))
bgcolor(strategy.position_size > 0 ? color.new(color.blue, 97) : 
     strategy.position_size < 0 ? color.new(color.orange, 97) : na)
bgcolor(hitDailyTarget ? color.new(color.green, 92) : hitLossLimit ? color.new(color.red, 92) : na)

// ═══════════════════════════════════════════════════════════
// 📊 STATS TABLE
// ═══════════════════════════════════════════════════════════

var table stats = table.new(position.top_right, 2, 11, border_width=1)

if barstate.islast
    // Header
    table.cell(stats, 0, 0, "📊 LONG & SHORT", bgcolor=color.blue, text_color=color.white, text_size=size.small)
    table.cell(stats, 1, 0, "STRATEGY", bgcolor=color.blue, text_color=color.white, text_size=size.small)
    
    // Trades
    table.cell(stats, 0, 1, "Trades", text_size=size.small)
    table.cell(stats, 1, 1, str.tostring(todayTrades) + "/" + str.tostring(maxTradesPerDay), 
         bgcolor=todayTrades >= maxTradesPerDay ? color.red : color.green, text_color=color.white, text_size=size.small)
    
    // Losses
    table.cell(stats, 0, 2, "Losses", text_size=size.small)
    table.cell(stats, 1, 2, str.tostring(consecutiveLosses), 
         bgcolor=consecutiveLosses >= maxConsecutiveLosses ? color.red : color.green, 
         text_color=color.white, text_size=size.small)
    
    // P&L
    pnlPct = (todayPnL / strategy.initial_capital) * 100
    table.cell(stats, 0, 3, "P&L", text_size=size.small)
    table.cell(stats, 1, 3, str.tostring(todayPnL, "#,###") + "\n" + str.tostring(pnlPct, "#.##") + "%", 
         bgcolor=todayPnL > 0 ? color.green : todayPnL < 0 ? color.red : color.gray, 
         text_color=color.white, text_size=size.small)
    
    // Position
    table.cell(stats, 0, 4, "Position", text_size=size.small)
    posText = strategy.position_size > 0 ? "LONG\n" + str.tostring(strategy.position_size) : 
         strategy.position_size < 0 ? "SHORT\n" + str.tostring(math.abs(strategy.position_size)) : "FLAT"
    posBg = strategy.position_size > 0 ? color.blue : strategy.position_size < 0 ? color.orange : color.gray
    table.cell(stats, 1, 4, posText, bgcolor=posBg, text_color=color.white, text_size=size.small)
    
    if strategy.position_size != 0
        // Entry
        table.cell(stats, 0, 5, "Entry", text_size=size.small)
        table.cell(stats, 1, 5, str.tostring(entryPrice, "#.##"), text_size=size.small)
        
        // Live P&L
        livePnL = strategy.position_size > 0 ? (close - entryPrice) * strategy.position_size : 
             (entryPrice - close) * math.abs(strategy.position_size)
        livePct = strategy.position_size > 0 ? ((close - entryPrice) / entryPrice) * 100 : 
             ((entryPrice - close) / entryPrice) * 100
        table.cell(stats, 0, 6, "Live P&L", text_size=size.small)
        table.cell(stats, 1, 6, str.tostring(livePnL, "#,###") + "\n" + str.tostring(livePct, "#.##") + "%", 
             bgcolor=livePnL > 0 ? color.green : color.red, text_color=color.white, text_size=size.small)
        
        // Bars
        bars = bar_index - entryBar
        table.cell(stats, 0, 7, "Bars", text_size=size.small)
        table.cell(stats, 1, 7, str.tostring(bars), text_size=size.small)
        
        // Stop
        table.cell(stats, 0, 8, "Stop", text_size=size.small)
        table.cell(stats, 1, 8, str.tostring(trailActive ? trailStop : stopLoss, "#.##") + 
             (trailActive ? " 🟡" : ""), text_size=size.small)
        
        // T1
        table.cell(stats, 0, 9, "T1", text_size=size.small)
        table.cell(stats, 1, 9, str.tostring(target1, "#.##") + (target1Hit ? " ✓" : ""), text_size=size.small)
        
        // T2
        table.cell(stats, 0, 10, "T2", text_size=size.small)
        table.cell(stats, 1, 10, str.tostring(target2, "#.##"), text_size=size.small)

// ═══════════════════════════════════════════════════════════
// 🔔 ALERTS
// ═══════════════════════════════════════════════════════════

alertcondition(longSignal, "🟢 LONG SIGNAL", "LONG Entry Signal")
alertcondition(shortSignal, "🔴 SHORT SIGNAL", "SHORT Entry Signal")
alertcondition(longReversal or shortReversal, "⚠️ REVERSAL", "Reversal Detected")