//@version=6 strategy("XAUUSD AI 911 V3 PRIMOV2 — 911 SUPREME (Triple Trend + Regime2 + Adaptive RR/Trail + Structure + Day Kill-Switch)", overlay=true, initial_capital=20000, default_qty_type=strategy.percent_of_equity, default_qty_value=10, pyramiding=0, calc_on_order_fills=true, calc_on_every_tick=true, max_labels_count=500, max_lines_count=500) // ===== Groups ===== groupSig = "Signals" groupATR = "ATR / Risk" groupTrail = "Trailing" groupSafe = "Safety Exits" groupQuick = "Quick ATR Exits" groupExec = "Execution Control" groupAuto = "Auto-Trade (Webhook JSON)" groupUSD = "USD Display" groupAlert = "Alert Throttle" groupMTF = "Higher TF Filter" groupReg = "Regime / Volatility" groupSess = "Session Guard" groupSend = "Webhook SL/TP (ATRx)" groupKS = "Day Kill-Switch" // ===== R:R base ===== useRR = input.bool(true, "Use Risk:Reward for TP", group=groupATR) rrBase= input.float(1.8, "Base RR", step=0.1, group=groupATR) // ===== Base Signals ===== channelLen = input.int(5, "Channel Length", group=groupSig) lrLen = input.int(90, "LR Filter Length", group=groupSig) emaLen = input.int(200,"EMA Filter Length", group=groupSig) // ===== ATR / Risk ===== atrLen = input.int(9, "ATR Length", group=groupATR) useTrailing= input.bool(true, "Use Trailing Stop (ATR)", group=groupTrail) trailMult = input.float(0.8, "ATR x for Trailing", step=0.1, group=groupTrail) maxAdverseATR = input.float(2.5, "Force Close if Adverse > X*ATR", step=0.1, group=groupSafe) maxBarsHold = input.int(180, "Time Stop: Max Bars In Trade", group=groupSafe) beATR = input.float(0.8, "Breakeven when Gain >= X*ATR", step=0.1, group=groupSafe) takeATR = input.float(0.4, "Quick TP (ATRx)", step=0.1, group=groupQuick) stopATR = input.float(0.7, "Quick SL (ATRx)", step=0.1, group=groupQuick) // ===== Exec / Alerts ===== cooldownBars = input.int(0, "Cooldown bars after CLOSE", minval=0, group=groupExec) oneActionPerBar = input.bool(true, "One action per bar (alerts)", group=groupExec) coolCloseSec = input.int(3, "HARD cooldown for CLOSE (sec)", minval=0, group=groupExec) keyInp = input.string("MY_SECRET_911", "Webhook Key", group=groupAuto) lotInp = input.float(0.50, "Lot to send", step=0.01, group=groupAuto) devInp = input.int(500, "Deviation (points)", group=groupAuto) usdPerPointPerLot = input.float(100.0, "USD per 1 point per 1 lot (XAU)", step=1.0, group=groupUSD) maxAlertsPer3min = input.int(12, "Max alerts / 3 min", minval=1, maxval=15, group=groupAlert) minAlertSec = input.int(5, "Min seconds between alerts", minval=0, group=groupAlert) onePerBarLimit = input.bool(true, "At most 1 alert / bar", group=groupAlert) // ===== Webhook SL/TP (ATRx) ===== sendLevels = input.bool(true, "Send TP/SL with OPEN", group=groupSend) sendSLx = input.float(1.0, "SL = X*ATR (webhook)", step=0.1, group=groupSend) sendTPx = input.float(0.8, "TP = X*ATR (if not RR)", step=0.1, group=groupSend) maxWebhookATRx = input.float(0.2, "CAP max TP dist = X*ATR", step=0.1, group=groupSend) // ===== MTF Filter ===== useMTF = input.bool(true, "Use Higher TF Filter", group=groupMTF) mtfTF = input.string("60", "Higher TF (min)", group=groupMTF) // 60=1H mtfLenLR = input.int(120, "Higher TF LR Length", group=groupMTF) mtfConfirmPct = input.float(0.0, "HTF close vs LR %", step=0.1, group=groupMTF) // ===== Regime / Volatility Gate v2 ===== useRegime = input.bool(true, "Use DMI/ADX + ATR Gate", group=groupReg) diLen = input.int(14, "DI Length", group=groupReg) adxSmooth = input.int(14, "ADX Smoothing", group=groupReg) adxMin = input.float(10.0, "Min ADX", step=0.1, group=groupReg) atrCoolK = input.float(0.30, "Skip if ATR < k * SMA(ATR)", step=0.05, group=groupReg) atrHeatK = input.float(2.0, "Skip if ATR > k * SMA(ATR)", step=0.1, group=groupReg) useRSITrend = input.bool(true, "Add RSI Trend Gate", group=groupReg) rsiLen = input.int(14, "RSI Length", group=groupReg) rsiBull = input.float(55, "RSI ≥ for LONG", step=0.5, group=groupReg) rsiBear = input.float(45, "RSI ≤ for SHORT", step=0.5, group=groupReg) atrMA = ta.sma(ta.atr(atrLen), atrLen) // ===== Session Guard ===== useSess = input.bool(false, "Use Session Guard", group=groupSess) sessStr = input.session("0600-2200", "Trading Session (exchange tz)", group=groupSess) // ===== Day Kill-Switch ===== useDayKS = input.bool(true, "Enable Day Kill-Switch", group=groupKS) maxTradesDay = input.int(30, "Max trades per day", group=groupKS) lockWhenLimit = input.bool(true, "Lock when reached", group=groupKS) // ===== Core calcs ===== upBound = ta.highest(high, channelLen)[1] downBound = ta.lowest(low, channelLen)[1] lrLine = ta.linreg(close, lrLen, 0) ema200 = ta.ema(close, emaLen) atr = ta.atr(atrLen) isBullishLR = lrLine > lrLine[1] and close >= lrLine isBearishLR = lrLine < lrLine[1] and close <= lrLine isBullishEMA = close >= ema200 isBearishEMA = close <= ema200 // MTF mtfClose = useMTF ? request.security(syminfo.tickerid, mtfTF, close) : na mtfLR = useMTF ? request.security(syminfo.tickerid, mtfTF, ta.linreg(close, mtfLenLR, 0)) : na mtfBull = useMTF ? (mtfClose >= mtfLR * (1 + mtfConfirmPct/100.0)) : true mtfBear = useMTF ? (mtfClose <= mtfLR * (1 - mtfConfirmPct/100.0)) : true // Regime (DMI/ADX) [plusDI, minusDI, adxVal] = ta.dmi(diLen, adxSmooth) atrOK = (atr >= atrCoolK * atrMA) and (atr <= atrHeatK * atrMA) trendOK = adxVal >= adxMin // RSI Trend gate rsiVal = ta.rsi(close, rsiLen) rsiOK_L = not useRSITrend or (rsiVal >= rsiBull) rsiOK_S = not useRSITrend or (rsiVal <= rsiBear) // Session sessOK = useSess ? not na(time(timeframe.period, sessStr)) : true // Day key (for Kill-Switch) dayKey = year*10000 + month*100 + dayofmonth // ===== Visuals ===== plot(lrLine, "LR Trend", color=color.rgb(0,150,255), linewidth=2) plot(ema200, "EMA200", color=color.new(color.orange, 0)) plot(upBound, "Upper", color=color.new(color.lime, 0)) plot(downBound,"Lower", color=color.new(color.red, 0)) // ===== Helpers ===== f_clamp_long(float sl, float tp) => _sl = math.min(sl, close) _tp = math.max(tp, close) _sl := _sl >= _tp ? _tp - atr : _sl [_sl, _tp] f_clamp_short(float sl, float tp) => _sl = math.max(sl, close) _tp = math.min(tp, close) _tp := _tp >= _sl ? _sl - atr : _tp [_sl, _tp] round_tick(x) => t = syminfo.mintick math.round(x / t) * t // Combine trend + MTF + EMA + RSI gate isBullOK(mtfFlag) => isBullishLR and isBullishEMA and mtfFlag and (not useRSITrend or rsiOK_L) isBearOK(mtfFlag) => isBearishLR and isBearishEMA and mtfFlag and (not useRSITrend or rsiOK_S) // JSON f_json(string action, float slp, float tpp) => sl_str = sendLevels ? (na(slp) ? "null" : str.tostring(slp, format.mintick)) : "null" tp_str = sendLevels ? (na(tpp) ? "null" : str.tostring(tpp, format.mintick)) : "null" _s = '{"key":"' + keyInp + '","attach":true' _s += ',"symbol":"' + syminfo.ticker + '"' _s += ',"action":"' + action + '"' _s += ',"lot":' + str.tostring(lotInp) _s += ',"deviation":' + str.tostring(devInp) _s += ',"sl_price":' + sl_str _s += ',"tp_price":' + tp_str _s += '}' _s // ===== Alert throttle STATE ===== var int __bucket3m = na var int __bucketCount = 0 var int __lastAlertMs = na var int __lastAlertBar = na if barstate.isrealtime _now = timenow _bk = na(_now) ? __bucket3m : int(math.floor(_now / 180000)) if na(__bucket3m) or (not na(_bk) and _bk != __bucket3m) __bucket3m := _bk __bucketCount := 0 f_can_alert() => if not barstate.isrealtime true else _now = timenow _coolOk = na(_now) or na(__lastAlertMs) or (_now - __lastAlertMs) >= minAlertSec * 1000 _quotaOk = __bucketCount < maxAlertsPer3min _barOk = not onePerBarLimit or na(__lastAlertBar) or __lastAlertBar != bar_index _coolOk and _quotaOk and _barOk fire_alert(float slp, float tpp, string side) => _shouldFire = f_can_alert() if _shouldFire alert(f_json(side, slp, tpp), alert.freq_once_per_bar) _shouldFire // ===== Trade State & Kill-Switch ===== var int lastCloseBar = na var bool sentCloseThisFlat = false var bool wasFlat = strategy.position_size == 0 var int lastCloseMs = na var int tradesDayCount = 0 var int lastDayKey = na isNewDay = na(lastDayKey) or (dayKey != lastDayKey) if isNewDay tradesDayCount := 0 lastDayKey := dayKey flatNow = strategy.position_size == 0 canHardCooldownClose() => na(timenow) or na(lastCloseMs) or (timenow - lastCloseMs) >= coolCloseSec * 1000 canSendCloseNow() => barstate.isrealtime and canHardCooldownClose() and (na(lastCloseBar) or lastCloseBar != bar_index) and not sentCloseThisFlat // ===== Runtime ===== var float currentSL = na var float currentTP = na var int lastActionBar = na var string pendingSide = na // "L" or "S" // Adaptive RR/Trail by ADX (แทน math.clamp) _adxScaleRaw = adxVal / 25.0 adxScale = math.min(math.max(_adxScaleRaw, 0.7), 2.2) _rrEffRaw = rrBase * adxScale rrEff = math.min(math.max(_rrEffRaw, 1.2), 3.0) _trailEffRaw = trailMult * (0.8 + (adxScale - 1.0) * 0.5) trailEff = math.min(math.max(_trailEffRaw, 0.4), 1.6) // Seed SL/TP (เพื่อตรงกับ webhook) newPos = strategy.position_size != 0 and strategy.position_size[1] == 0 lostLevels = strategy.position_size != 0 and (na(currentSL) or na(currentTP)) if (newPos or lostLevels) riskDist = atr * sendSLx tpDistRR = riskDist * rrEff tpDistFix = atr * sendTPx tpDistRaw = useRR ? tpDistRR : tpDistFix capDist = atr * maxWebhookATRx tpDist = math.min(tpDistRaw, capDist) if strategy.position_size > 0 currentSL := strategy.position_avg_price - riskDist currentTP := strategy.position_avg_price + tpDist else currentSL := strategy.position_avg_price + riskDist currentTP := strategy.position_avg_price - tpDist if newPos sentCloseThisFlat := false canActThisBar = not oneActionPerBar or na(lastActionBar) or bar_index > lastActionBar notCooldown = na(lastActionBar) or (bar_index - lastActionBar >= cooldownBars) dayKSLock = useDayKS and lockWhenLimit and (tradesDayCount >= maxTradesDay) tradableAll = sessOK and (useRegime ? (atrOK and trendOK) : true) and not dayKSLock // ===== Close conditions ===== closeSignal = false closedThisBar = false if canActThisBar and tradableAll if strategy.position_size > 0 and isBearOK(mtfBear) strategy.close("L", comment="ReverseClose") closeSignal := true closedThisBar := true pendingSide := "S" lastActionBar := bar_index else if strategy.position_size < 0 and isBullOK(mtfBull) strategy.close("S", comment="ReverseClose") closeSignal := true closedThisBar := true pendingSide := "L" lastActionBar := bar_index adverse = strategy.position_size > 0 ? (strategy.position_avg_price - close) / atr : strategy.position_size < 0 ? (close - strategy.position_avg_price) / atr : 0.0 barsInPos = ta.barssince(strategy.position_size == 0) if strategy.position_size > 0 and adverse > maxAdverseATR strategy.close("L", comment="ATR-Kill") closeSignal := true closedThisBar := true lastActionBar := bar_index if strategy.position_size < 0 and adverse > maxAdverseATR strategy.close("S", comment="ATR-Kill") closeSignal := true closedThisBar := true lastActionBar := bar_index if strategy.position_size != 0 and barsInPos >= maxBarsHold strategy.close(strategy.position_size > 0 ? "L" : "S", comment="TimeStop") closeSignal := true closedThisBar := true lastActionBar := bar_index // Breakeven bump gain = strategy.position_size > 0 ? (close - strategy.position_avg_price) / atr : strategy.position_size < 0 ? (strategy.position_avg_price - close) / atr : 0.0 if strategy.position_size > 0 and gain >= beATR currentSL := math.max(currentSL, strategy.position_avg_price) if strategy.position_size < 0 and gain >= beATR currentSL := math.min(currentSL, strategy.position_avg_price) // Quick ATR exits gainATR = strategy.position_size > 0 ? (close - strategy.position_avg_price) / atr : strategy.position_size < 0 ? (strategy.position_avg_price - close) / atr : 0.0 if strategy.position_size != 0 and gainATR >= takeATR strategy.close(strategy.position_size > 0 ? "L" : "S", comment="QuickTP") closeSignal := true closedThisBar := true lastActionBar := bar_index if strategy.position_size != 0 and (-gainATR) >= stopATR strategy.close(strategy.position_size > 0 ? "L" : "S", comment="QuickSL") closeSignal := true closedThisBar := true lastActionBar := bar_index // Manage exits (SL/TP + adaptive trailing) if strategy.position_size > 0 if useTrailing and not na(currentSL) trailStop = close - (atr * trailEff) currentSL := math.max(currentSL, trailStop) [lsl, ltp] = f_clamp_long(currentSL, currentTP) strategy.exit("XL_Exit", from_entry="L", stop=lsl, limit=ltp) else if strategy.position_size < 0 if useTrailing and not na(currentSL) trailStop = close + (atr * trailEff) currentSL := math.min(currentSL, trailStop) [ssl, stp] = f_clamp_short(currentSL, currentTP) strategy.exit("XS_Exit", from_entry="S", stop=ssl, limit=stp) // ===== Entry modes ===== entryMode = input.string("Retest", "Entry Mode", options=["Retest","MarketCross","BreakoutStop"], group=groupExec) crossUp = ta.crossover(close, upBound) crossDown = ta.crossunder(close, downBound) retestUp = (low <= upBound and close > upBound) retestDown = (high >= downBound and close < downBound) // Swing checks (structure awareness) swingLen = input.int(3, "Swing len for structure", minval=1, group=groupSig) prevSwingH = ta.highest(high, swingLen)[1] prevSwingL = ta.lowest(low, swingLen)[1] longStructOK = close > prevSwingH shortStructOK = close < prevSwingL // webhook distances riskDist_w = atr * sendSLx tpDistRR_w = riskDist_w * rrEff tpDistFix_w = atr * sendTPx tpDistRaw_w = useRR ? tpDistRR_w : tpDistFix_w capDist_w = atr * maxWebhookATRx tpDist_w = math.min(tpDistRaw_w, capDist_w) // market-style rounded levels mk_wl_sl = round_tick(close - riskDist_w) mk_ws_sl = round_tick(close + riskDist_w) mk_wl_tp = round_tick(close + tpDist_w) mk_ws_tp = round_tick(close - tpDist_w) // planned for stop entries plannedLong = na(upBound) ? na : (upBound + syminfo.mintick) plannedShort = na(downBound) ? na : (downBound - syminfo.mintick) wl_sl = na(plannedLong) ? na : round_tick(plannedLong - riskDist_w) ws_sl = na(plannedShort) ? na : round_tick(plannedShort + riskDist_w) wl_tp = na(plannedLong) ? na : round_tick(plannedLong + tpDist_w) ws_tp = na(plannedShort) ? na : round_tick(plannedShort - tpDist_w) // ===== OPEN (respect Kill-Switch) ===== allowRealtimeOpen = barstate.isrealtime okBarConfirm = barstate.isconfirmed or allowRealtimeOpen canOpenCore = (strategy.position_size == 0) and (notCooldown or closedThisBar or not na(pendingSide)) and canActThisBar and okBarConfirm and tradableAll canOpenNow = canOpenCore and (not useDayKS or tradesDayCount < maxTradesDay) if canOpenNow strategy.cancel_all() // pendingSide priority if not na(pendingSide) if pendingSide == "L" and isBullOK(mtfBull) and longStructOK if entryMode == "BreakoutStop" and not na(plannedLong) strategy.entry("L", strategy.long, stop=plannedLong) if fire_alert(wl_sl, wl_tp, "LONG") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "MarketCross" and (crossUp or close > upBound) strategy.entry("L", strategy.long) if fire_alert(mk_wl_sl, mk_wl_tp, "LONG") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "Retest" and retestUp strategy.entry("L", strategy.long) if fire_alert(mk_wl_sl, mk_wl_tp, "LONG") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 if strategy.position_size != 0 pendingSide := na lastActionBar := bar_index else if pendingSide == "S" and isBearOK(mtfBear) and shortStructOK if entryMode == "BreakoutStop" and not na(plannedShort) strategy.entry("S", strategy.short, stop=plannedShort) if fire_alert(ws_sl, ws_tp, "SHORT") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "MarketCross" and (crossDown or close < downBound) strategy.entry("S", strategy.short) if fire_alert(mk_ws_sl, mk_ws_tp, "SHORT") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "Retest" and retestDown strategy.entry("S", strategy.short) if fire_alert(mk_ws_sl, mk_ws_tp, "SHORT") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 if strategy.position_size != 0 pendingSide := na lastActionBar := bar_index // normal open if strategy.position_size == 0 if isBullOK(mtfBull) and longStructOK if entryMode == "BreakoutStop" and not na(plannedLong) strategy.entry("L", strategy.long, stop=plannedLong) if fire_alert(wl_sl, wl_tp, "LONG") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "MarketCross" and (crossUp or close > upBound) strategy.entry("L", strategy.long) if fire_alert(mk_wl_sl, mk_wl_tp, "LONG") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "Retest" and retestUp strategy.entry("L", strategy.long) if fire_alert(mk_wl_sl, mk_wl_tp, "LONG") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 lastActionBar := bar_index else if isBearOK(mtfBear) and shortStructOK if entryMode == "BreakoutStop" and not na(plannedShort) strategy.entry("S", strategy.short, stop=plannedShort) if fire_alert(ws_sl, ws_tp, "SHORT") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "MarketCross" and (crossDown or close < downBound) strategy.entry("S", strategy.short) if fire_alert(mk_ws_sl, mk_ws_tp, "SHORT") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 else if entryMode == "Retest" and retestDown strategy.entry("S", strategy.short) if fire_alert(mk_ws_sl, mk_ws_tp, "SHORT") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index tradesDayCount += 1 lastActionBar := bar_index // Transition: position -> flat (mark CLOSE once) justClosed = (not wasFlat) and strategy.position_size == 0 and strategy.position_size[1] != 0 wasFlat := (strategy.position_size == 0) if justClosed closeSignal := true // SINGLE dispatch for CLOSE if closeSignal and canSendCloseNow() if fire_alert(na, na, "CLOSE") if not na(timenow) __lastAlertMs := timenow __bucketCount += 1 __lastAlertBar := bar_index lastCloseBar := bar_index sentCloseThisFlat := true if barstate.isrealtime and not na(timenow) lastCloseMs := timenow // ===== HUD ===== inPos = strategy.position_size != 0 dir = strategy.position_size > 0 ? 1.0 : strategy.position_size < 0 ? -1.0 : 0.0 avgPrice = strategy.position_avg_price pnl_points = inPos ? (close - avgPrice) * dir : 0.0 pnl_usd = pnl_points * usdPerPointPerLot * lotInp atr_usd = atr * usdPerPointPerLot * lotInp tp_usd = takeATR * atr_usd sl_usd = stopATR * atr_usd to_tp_usd = inPos ? math.max(0, (takeATR - (dir > 0 ? (close - avgPrice)/atr : (avgPrice - close)/atr)) * atr_usd) : na to_sl_usd = inPos ? math.max(0, (stopATR + (dir > 0 ? (close - avgPrice)/atr : (avgPrice - close)/atr)) * atr_usd) : na var label _hud = na if barstate.islast if not na(_hud) label.delete(_hud) dirTxt = strategy.position_size > 0 ? "LONG" : strategy.position_size < 0 ? "SHORT" : "FLAT" regTxt = "ADX:" + str.tostring(adxVal, "#.0") regTxt += atrOK ? " ATROK" : " ATR!" regTxt += (useRSITrend ? " RSI:" + str.tostring(rsiVal, "#.0") : "") ksTxt = useDayKS ? (" | TradesToday: " + str.tostring(tradesDayCount) + "/" + str.tostring(maxTradesDay) + (dayKSLock ? " (LOCK)" : "")) : "" hud = "Tradable: " + (tradableAll ? (dayKSLock ? "LOCKED" : "YES") : "NO") + ksTxt hud += "\nDir: " + dirTxt + " | RR_eff: " + str.tostring(rrEff, "#.00") + " | TrailEff: " + str.tostring(trailEff, "#.00") hud += "\nATR ≈ $" + str.tostring(atr_usd, format.mintick) + " | " + regTxt hud += "\nQuick TP/SL: +" + str.tostring(tp_usd, format.mintick) + " / -" + str.tostring(sl_usd, format.mintick) hud += "\nPnL$ now: " + str.tostring(pnl_usd, format.mintick) if inPos hud += "\nTo TP: $" + str.tostring(to_tp_usd, format.mintick) + " To SL: $" + str.tostring(to_sl_usd, format.mintick) if not na(pendingSide) hud += "\nPending: " + pendingSide _hud := label.new(bar_index, high, hud, yloc=yloc.abovebar, style=label.style_label_down)