//@version=6 strategy("ORB Sentinel (V2)", overlay=true, initial_capital=10000, currency=currency.USD, use_bar_magnifier=true, calc_on_every_tick=true, pyramiding=0, process_orders_on_close=false, max_lines_count=500) string TZ = "America/New_York" grpOrb = "===== ORB =====" orbMode = input.string("V1 09:15-09:30", "ORB Version", options=[ "V1 09:15-09:30", "V4 09:29-09:40", "V5 09:10-09:35", "V6 09:00-09:30", "V7 09:20-09:30" ], group=grpOrb) grpRisk = "===== RISK =====" riskUsd = input.float(2800, "Risk ($)", minval=1, step=1, group=grpRisk) grpMode = "===== TRADE MODE =====" tradeMode = input.string("Try Again", "After stop-out", options=["Try Again", "Reversal", "Either Direction", "No more trades"], group=grpMode, tooltip="Try Again: after a stop-out, allow one more trade in the SAME direction.\nReversal: after a stop-out, allow one more trade in the OPPOSITE direction.\nEither Direction: after a stop-out, allow one more trade long OR short (rules depend on Follow-up mode).\nNo more trades: disable the 2nd trade after any stop-out.\nMax 2 trades per day.") followUpMode = input.string("Match Initial Signal Size", "Follow-up mode (trade #2)", options=["Match Initial Signal Size", "New Signal Size"], group=grpMode, tooltip="Match Initial Signal Size: current behavior (reuse trade #1 initial SL distance + position size).\nNew Signal Size: treat trade #2 like a new signal.\n- If the stop-out 5m candle CLOSES inside the ORB, wait for a fresh 5m breakout.\n- If it CLOSES outside the ORB, re-enter on the next 5m close (no new breakout required).") grpTP = "===== TAKE PROFITS =====" tp1On = input.bool(false, "TP1", group=grpTP, inline="tp1") tp1R = input.float(3.0, "@ (R)", minval=0.1, step=0.1, group=grpTP, inline="tp1") tp1Pct = input.float(40, "Size (%)", minval=0, maxval=100, step=1, group=grpTP, inline="tp1") tp2On = input.bool(false, "TP2", group=grpTP, inline="tp2") tp2R = input.float(2.5, "@ (R)", minval=0.1, step=0.1, group=grpTP, inline="tp2") tp2Pct = input.float(25, "Size (%)", minval=0, maxval=100, step=1, group=grpTP, inline="tp2") hardTpR = input.float(10.0, "Hard TP @ (R)", minval=0.1, step=0.1, group=grpTP) grpSL = "===== STOP LOSSES =====" slMode = input.string("Signal candle base", "Stop Loss", options=[ "Signal candle base", "Extend by % ORB" ], tooltip="Signal candle base: uses the signal candle low/high.\nExtend by % ORB: extends SL by a % of the ORB range.", group=grpSL) slExtendPct = input.float(20, "Extend % of ORB", minval=0, step=1, group=grpSL) minStopTicks = input.int(50, "Min SL size (ticks)", minval=1, group=grpSL, tooltip="If the computed initial SL is tighter than this many ticks, we widen it to max(50% of ORB width, Min SL ticks) to prevent tiny stops (often caused by bad historical prints) from exploding position sizing.") grpTSL = "===== TSL (TRAILING STOP) =====" tsl1On = input.bool(false, "TSL #1", group=grpTSL, inline="tsl1") tsl1TrigR = input.float(1.0, "Trig (R)", minval=0.1, step=0.1, group=grpTSL, inline="tsl1") tsl1ToR = input.float(-0.5, "SL @ (R from entry)", step=0.1, group=grpTSL, inline="tsl1", tooltip="When price reaches Trig (R), move SL to Entry + (R * value) for longs, Entry - (R * value) for shorts.\n+R moves SL in favor of trade, -R moves it against.") tsl2On = input.bool(false, "TSL #2", group=grpTSL, inline="tsl2") tsl2TrigR = input.float(2.0, "Trig (R)", minval=0.1, step=0.1, group=grpTSL, inline="tsl2") tsl2ToR = input.float(0.0, "SL @ (R from entry)", step=0.1, group=grpTSL, inline="tsl2") tsl3On = input.bool(false, "TSL #3", group=grpTSL, inline="tsl3") tsl3TrigR = input.float(3.0, "Trig (R)", minval=0.1, step=0.1, group=grpTSL, inline="tsl3") tsl3ToR = input.float(1.0, "SL @ (R from entry)", step=0.1, group=grpTSL, inline="tsl3") tsl4On = input.bool(false, "TSL #4", group=grpTSL, inline="tsl4") tsl4TrigR = input.float(4.0, "Trig (R)", minval=0.1, step=0.1, group=grpTSL, inline="tsl4") tsl4ToR = input.float(2.0, "SL @ (R from entry)", step=0.1, group=grpTSL, inline="tsl4") tsl5On = input.bool(false, "TSL #5", group=grpTSL, inline="tsl5") tsl5TrigR = input.float(5.0, "Trig (R)", minval=0.1, step=0.1, group=grpTSL, inline="tsl5") tsl5ToR = input.float(3.0, "SL @ (R from entry)", step=0.1, group=grpTSL, inline="tsl5") grpTime = "===== TIME WINDOWS =====" tradeEndH = input.int(16, "Trade end hour (NY)", minval=0, maxval=23, group=grpTime) tradeEndM = input.int(55, "Trade end minute (NY)", minval=0, maxval=59, group=grpTime, tooltip="Set to 18:00 for 6pm NY time.") grpGfx = "===== GRAPHICS & COLORS =====" cOrbRec = input.color(color.yellow, "ORB recording lines", group=grpGfx) cOrbExt = input.color(color.white, "ORB extension lines", group=grpGfx) cTsl = input.color(color.red, "TSL line + labels", group=grpGfx) cEntry = input.color(color.aqua, "Entry line + label", group=grpGfx) cPtp = input.color(color.orange, "PTP lines + labels", group=grpGfx) cTp = input.color(color.lime, "TP line + label", group=grpGfx) cBoxTpBorder = input.color(color.new(color.green, 90), "TP box border", group=grpGfx) cBoxTpBg = input.color(color.new(color.green, 90), "TP box fill", group=grpGfx) cBoxSlBorder = input.color(color.new(color.red, 90), "SL box border", group=grpGfx) cBoxSlBg = input.color(color.new(color.red, 90), "SL box fill", group=grpGfx) // ---------- Time windows (NY) ---------- int y = year(time, TZ) int m = month(time, TZ) int d = dayofmonth(time, TZ) int orbStartH = 9, orbStartM = 15, orbEndH = 9, orbEndM = 30 if orbMode == "V4 09:29-09:40" orbStartH := 9, orbStartM := 29, orbEndH := 9, orbEndM := 40 else if orbMode == "V5 09:10-09:35" orbStartH := 9, orbStartM := 10, orbEndH := 9, orbEndM := 35 else if orbMode == "V6 09:00-09:30" orbStartH := 9, orbStartM := 0, orbEndH := 9, orbEndM := 30 else if orbMode == "V7 09:20-09:30" orbStartH := 9, orbStartM := 20, orbEndH := 9, orbEndM := 30 int orbStartTs = timestamp(TZ, y, m, d, orbStartH, orbStartM) int orbEndTs = timestamp(TZ, y, m, d, orbEndH, orbEndM) int entryCutoffTs = timestamp(TZ, y, m, d, 11, 0) int tradeEndTs = timestamp(TZ, y, m, d, tradeEndH, tradeEndM) bool inOrb = time >= orbStartTs and time < orbEndTs bool afterTradeEnd = time_close >= tradeEndTs bool tradeEndJustHit = afterTradeEnd and not afterTradeEnd[1] // ---------- ORB hi/lo per day ---------- int dayKey = y * 10000 + m * 100 + d bool newDay = na(dayKey[1]) or dayKey != dayKey[1] var int lastEntryTime5 = na // Per-day trade limiting (max 2 trades/day) var int tradesToday = 0 var int firstTradeDir = 0 // +1 = long, -1 = short var bool firstTradeStopped = false var int lastStopoutTime5 = na // start time of the 5m candle where stop-out happened var float firstSlDist = na // absolute price distance (entry to initial SL) for trade #1 var float firstQty = na // position size used for trade #1 (reused for trade #2) var int stopoutBarTime5 = na // start time of the 5m candle where trade #1 stopped out var bool stopoutCloseKnown = false // becomes true once stopout bar has closed var bool stopoutClosedInsideOrb = false var int stopoutNextBarTime5 = na // start time of the 5m bar AFTER the stopout bar (set when stopout bar closes) var float orbHi = na var float orbLo = na if newDay orbHi := na orbLo := na lastEntryTime5 := na tradesToday := 0 firstTradeDir := 0 firstTradeStopped := false lastStopoutTime5 := na firstSlDist := na firstQty := na stopoutBarTime5 := na stopoutCloseKnown := false stopoutClosedInsideOrb := false stopoutNextBarTime5 := na strategy.cancel("L") strategy.cancel("S") if inOrb orbHi := na(orbHi) ? high : math.max(orbHi, high) orbLo := na(orbLo) ? low : math.min(orbLo, low) // ORB lines (keep the original line format) // Yellow: recording window (start -> end). White: extension (end -> trade end). // Keep bounded so multi-year charts don't exceed max_lines_count. var line[] orbLines = array.new_line() f_storeOrbLine(line _l) => array.push(orbLines, _l) if array.size(orbLines) > 250 line.delete(array.shift(orbLines)) bool orbJustEnded = not inOrb and inOrb[1] if orbJustEnded and not na(orbHi) and not na(orbLo) // Recording window in yellow (final ORB hi/lo). f_storeOrbLine(line.new(x1=orbStartTs, y1=orbHi, x2=orbEndTs, y2=orbHi, xloc=xloc.bar_time, color=cOrbRec, width=2)) f_storeOrbLine(line.new(x1=orbStartTs, y1=orbLo, x2=orbEndTs, y2=orbLo, xloc=xloc.bar_time, color=cOrbRec, width=2)) // Extension in white. f_storeOrbLine(line.new(x1=orbEndTs, y1=orbHi, x2=tradeEndTs, y2=orbHi, xloc=xloc.bar_time, color=cOrbExt, width=2)) f_storeOrbLine(line.new(x1=orbEndTs, y1=orbLo, x2=tradeEndTs, y2=orbLo, xloc=xloc.bar_time, color=cOrbExt, width=2)) // ---------- Signals (5m close based) ---------- bool inPos = strategy.position_size != 0 bool flat = strategy.position_size == 0 int time5 = request.security(syminfo.tickerid, "5", time, barmerge.gaps_off, barmerge.lookahead_off) int timeClose5 = request.security(syminfo.tickerid, "5", time_close, barmerge.gaps_off, barmerge.lookahead_off) float close5 = request.security(syminfo.tickerid, "5", close, barmerge.gaps_off, barmerge.lookahead_off) float high5 = request.security(syminfo.tickerid, "5", high, barmerge.gaps_off, barmerge.lookahead_off) float low5 = request.security(syminfo.tickerid, "5", low, barmerge.gaps_off, barmerge.lookahead_off) bool isNew5Close = ta.change(time5) != 0 var float prevClose5 = na var float lastClose5 = na if isNew5Close prevClose5 := lastClose5 lastClose5 := close5 // For follow-up mode "New Signal Size": determine whether the STOP-OUT 5m candle closed inside ORB. // Note: `isNew5Close` fires at the FIRST tick of the new 5m bar; at that moment, `close5` is the prior 5m bar close. if firstTradeStopped and not stopoutCloseKnown and not na(stopoutBarTime5) and isNew5Close and time5[1] == stopoutBarTime5 and not na(orbHi) and not na(orbLo) stopoutClosedInsideOrb := close5 <= orbHi and close5 >= orbLo stopoutCloseKnown := true stopoutNextBarTime5 := time5 bool inTradeWnd5 = time5 >= orbEndTs and time5 < tradeEndTs bool allowNewEntries5 = timeClose5 <= entryCutoffTs bool ready5 = flat and inTradeWnd5 and allowNewEntries5 and not na(orbHi) and not na(orbLo) bool longSignal = ready5 and isNew5Close and close5 > orbHi and prevClose5 <= orbHi bool shortSignal = ready5 and isNew5Close and close5 < orbLo and prevClose5 >= orbLo barcolor((longSignal or shortSignal) ? color.yellow : na) // ---------- Trade state ---------- var float tSL = na var float tSLInit = na var float tEntry = na var float tR0 = na var float tTP = na var int tEntryBar = na var box tradeBoxTP = na var box tradeBoxSL = na var bool slLock = false var float ptp1Px = na var float ptp2Px = na float orbRange = not na(orbHi) and not na(orbLo) ? (orbHi - orbLo) : na grpLbl = "===== END-OF-LINE LABELS =====" endLblOn = input.bool(true, "Show TSL/PTP end labels", group=grpLbl) endLblSizeOpt = input.string("Small", "Label size", options=["Tiny", "Small"], group=grpLbl) keepLblTrades = input.int(5, "Keep labels for last N trades", minval=1, maxval=50, group=grpLbl) // TSL/PTP line objects (restore original behavior) var line tslSegLine = na var float tslSegPx = na var int tslSegStart = na var line ptp1Line = na var line ptp2Line = na var line entryLine = na var line tpLine = na // Persist labels for last N trades var label[] tslLbls = array.new_label() var label[] ptp1Lbls = array.new_label() var label[] ptp2Lbls = array.new_label() var label[] entryLbls = array.new_label() var label[] tpLbls = array.new_label() // Current-trade labels (so we can keep them aligned to the right edge of the trade box) var label tslCurLbl = na var label ptp1CurLbl = na var label ptp2CurLbl = na var label entryCurLbl = na var label tpCurLbl = na f_pushLbl(label[] _arr, label _l) => array.push(_arr, _l) if array.size(_arr) > keepLblTrades label _old = array.shift(_arr) if not na(_old) label.delete(_old) // Note: labels are persistent per-trade; we also keep "current trade" labels aligned to the box's right edge. if not inPos and inPos[1] // Detect whether the last trade closed via SL (a "failed" trade for Try Again / Reversal logic) if strategy.closedtrades > 0 int _i = strategy.closedtrades - 1 string _exitC = strategy.closedtrades.exit_comment(_i) if _exitC == "SL" // Only the first trade's stop-out enables a second trade (max 2/day). if tradesToday == 1 and not firstTradeStopped firstTradeStopped := true lastStopoutTime5 := time5 stopoutBarTime5 := time5 stopoutCloseKnown := false stopoutClosedInsideOrb := false stopoutNextBarTime5 := na tSL := na tSLInit := na tEntry := na tR0 := na tTP := na tEntryBar := na lastEntryTime5 := na slLock := false // Critical: cancel leftover exits so stops can't trigger after flat (and open a new position). strategy.cancel("L_SL") strategy.cancel("S_SL") strategy.cancel("L_TP1") strategy.cancel("L_TP2") strategy.cancel("L_TP") strategy.cancel("S_TP1") strategy.cancel("S_TP2") strategy.cancel("S_TP") if not na(tradeBoxTP) box.set_right(tradeBoxTP, bar_index) if not na(tradeBoxSL) box.set_right(tradeBoxSL, bar_index) // Freeze current-trade labels at the right edge of the (finalized) trade box int xr = not na(tradeBoxTP) ? box.get_right(tradeBoxTP) : bar_index if not na(tslCurLbl) label.set_x(tslCurLbl, xr) if not na(ptp1CurLbl) label.set_x(ptp1CurLbl, xr) if not na(ptp2CurLbl) label.set_x(ptp2CurLbl, xr) if not na(entryCurLbl) label.set_x(entryCurLbl, xr) if not na(tpCurLbl) label.set_x(tpCurLbl, xr) tslCurLbl := na ptp1CurLbl := na ptp2CurLbl := na entryCurLbl := na tpCurLbl := na ptp1Px := na ptp2Px := na // Finalize lines for this trade if not na(tslSegLine) line.set_x2(tslSegLine, bar_index) if not na(ptp1Line) line.set_x2(ptp1Line, bar_index) if not na(ptp2Line) line.set_x2(ptp2Line, bar_index) if not na(entryLine) line.set_x2(entryLine, bar_index) if not na(tpLine) line.set_x2(tpLine, bar_index) tslSegLine := na tslSegPx := na tslSegStart := na ptp1Line := na ptp2Line := na entryLine := na tpLine := na // ---------- Entries (5m close) ---------- bool canEnterThis5 = na(lastEntryTime5) or time5 != lastEntryTime5 if flat and inTradeWnd5 and canEnterThis5 bool canTakeFirst = tradesToday == 0 // 2nd trade rules (only after 1st trade stopped out). // - Try Again: if a 5m candle closes beyond the SAME ORB side as the first trade, allow re-entry on that close. // - Reversal: if a 5m candle closes beyond the OPPOSITE ORB side, allow entry on that close. // If a candle closes back INSIDE the ORB, then the next allowed entry naturally requires a new close beyond ORB again. bool canTakeSecond = tradesToday == 1 and firstTradeStopped and not na(lastStopoutTime5) and time5 >= lastStopoutTime5 and tradeMode != "No more trades" int secondDir = tradeMode == "Try Again" ? firstTradeDir : tradeMode == "Reversal" ? -firstTradeDir : 0 bool allowEitherDir2 = tradeMode == "Either Direction" bool wantLong2 = canTakeSecond and (allowEitherDir2 or secondDir == 1) bool wantShort2 = canTakeSecond and (allowEitherDir2 or secondDir == -1) // Follow-up signal selection: // - Match Initial Signal Size: "match original sizing" behavior -> any 5m close beyond ORB. // - New Signal Size: if stopout candle closed inside ORB, wait for a fresh breakout (crossing out). // If stopout candle closed outside ORB, take the next 5m close if it's still outside (Either Direction ignores original dir). bool followUpIsMode2 = followUpMode == "New Signal Size" bool longSignal2 = false bool shortSignal2 = false bool stopKnown = stopoutCloseKnown bool isImmediateBar = stopKnown and not stopoutClosedInsideOrb and not na(stopoutNextBarTime5) and isNew5Close and time5[1] == stopoutNextBarTime5 bool immediateLong = ready5 and isImmediateBar and close5 > orbHi bool immediateShort = ready5 and isImmediateBar and close5 < orbLo if not followUpIsMode2 // Mode 1: allow any close beyond ORB (and also allow immediate re-entry if applicable). longSignal2 := stopKnown and (immediateLong or (ready5 and isNew5Close and close5 > orbHi)) shortSignal2 := stopKnown and (immediateShort or (ready5 and isNew5Close and close5 < orbLo)) else // Mode 2: breakout must be a cross; but if stopout closed outside, allow immediate next-bar close. bool breakoutLong = ready5 and isNew5Close and close5 > orbHi and prevClose5 <= orbHi bool breakoutShort = ready5 and isNew5Close and close5 < orbLo and prevClose5 >= orbLo longSignal2 := stopKnown and (immediateLong or breakoutLong) shortSignal2 := stopKnown and (immediateShort or breakoutShort) bool allowLong = (canTakeFirst and longSignal) or (wantLong2 and longSignal2) bool allowShort = (canTakeFirst and shortSignal) or (wantShort2 and shortSignal2) bool isFollowUpLong = wantLong2 and longSignal2 bool isFollowUpShort = wantShort2 and shortSignal2 if allowLong float sl = na float slSize = na float qty = na bool matchInitial = followUpMode == "Match Initial Signal Size" if isFollowUpLong and matchInitial and not na(firstSlDist) and firstSlDist > 0 slSize := firstSlDist sl := close5 - slSize float riskPerUnit = slSize * syminfo.pointvalue qty := not na(firstQty) ? firstQty : (riskPerUnit > 0 ? (riskUsd / riskPerUnit) : na) else sl := slMode == "Extend by % ORB" and not na(orbRange) ? (low5 - orbRange * (slExtendPct / 100.0)) : low5 slSize := math.abs(close5 - sl) float _tick = syminfo.mintick float _slTicks = _tick > 0 ? (slSize / _tick) : na float _minAbs = _tick * minStopTicks float _orbHalf = not na(orbRange) ? (orbRange * 0.5) : na float _fallbackAbs = not na(_orbHalf) ? math.max(_minAbs, _orbHalf) : _minAbs if not na(_slTicks) and _slTicks < minStopTicks slSize := _fallbackAbs sl := close5 - slSize float riskPerUnit = slSize * syminfo.pointvalue qty := riskPerUnit > 0 ? (riskUsd / riskPerUnit) : na tSL := sl tSLInit := sl tEntryBar := bar_index tEntry := close5 tR0 := math.abs(tEntry - tSLInit) tTP := tEntry + tR0 * hardTpR strategy.entry("L", strategy.long, qty=qty) if tradesToday == 0 firstTradeDir := 1 firstSlDist := slSize firstQty := qty tradesToday += 1 lastEntryTime5 := time5 tradeBoxTP := box.new(left=bar_index, top=tTP, right=bar_index, bottom=tEntry, xloc=xloc.bar_index, border_color=cBoxTpBorder, bgcolor=cBoxTpBg) tradeBoxSL := box.new(left=bar_index, top=tEntry, right=bar_index, bottom=tSL, xloc=xloc.bar_index, border_color=cBoxSlBorder, bgcolor=cBoxSlBg) else if allowShort float sl = na float slSize = na float qty = na bool matchInitial = followUpMode == "Match Initial Signal Size" if isFollowUpShort and matchInitial and not na(firstSlDist) and firstSlDist > 0 slSize := firstSlDist sl := close5 + slSize float riskPerUnit = slSize * syminfo.pointvalue qty := not na(firstQty) ? firstQty : (riskPerUnit > 0 ? (riskUsd / riskPerUnit) : na) else sl := slMode == "Extend by % ORB" and not na(orbRange) ? (high5 + orbRange * (slExtendPct / 100.0)) : high5 slSize := math.abs(close5 - sl) float _tick = syminfo.mintick float _slTicks = _tick > 0 ? (slSize / _tick) : na float _minAbs = _tick * minStopTicks float _orbHalf = not na(orbRange) ? (orbRange * 0.5) : na float _fallbackAbs = not na(_orbHalf) ? math.max(_minAbs, _orbHalf) : _minAbs if not na(_slTicks) and _slTicks < minStopTicks slSize := _fallbackAbs sl := close5 + slSize float riskPerUnit = slSize * syminfo.pointvalue qty := riskPerUnit > 0 ? (riskUsd / riskPerUnit) : na tSL := sl tSLInit := sl tEntryBar := bar_index tEntry := close5 tR0 := math.abs(tEntry - tSLInit) tTP := tEntry - tR0 * hardTpR strategy.entry("S", strategy.short, qty=qty) if tradesToday == 0 firstTradeDir := -1 firstSlDist := slSize firstQty := qty tradesToday += 1 lastEntryTime5 := time5 tradeBoxTP := box.new(left=bar_index, top=tEntry, right=bar_index, bottom=tTP, xloc=xloc.bar_index, border_color=cBoxTpBorder, bgcolor=cBoxTpBg) tradeBoxSL := box.new(left=bar_index, top=tSL, right=bar_index, bottom=tEntry, xloc=xloc.bar_index, border_color=cBoxSlBorder, bgcolor=cBoxSlBg) // ---------- Update levels from real fill ---------- if inPos and not na(tSL) tEntry := strategy.position_avg_price if na(tR0) and not na(tSLInit) tR0 := math.abs(tEntry - tSLInit) if strategy.position_size > 0 tTP := tEntry + tR0 * hardTpR else tTP := tEntry - tR0 * hardTpR if not na(tradeBoxTP) box.set_right(tradeBoxTP, bar_index) if not na(tradeBoxSL) box.set_right(tradeBoxSL, bar_index) if not na(tradeBoxTP) box.set_top(tradeBoxTP, math.max(tEntry, tTP)) box.set_bottom(tradeBoxTP, math.min(tEntry, tTP)) if not na(tradeBoxSL) // Keep SL box showing the INITIAL SL (background does not trail) box.set_top(tradeBoxSL, math.max(tEntry, tSLInit)) box.set_bottom(tradeBoxSL, math.min(tEntry, tSLInit)) // Extend PTP lines to the current bar (prices stay fixed) if not na(ptp1Line) line.set_x2(ptp1Line, bar_index) if not na(ptp2Line) line.set_x2(ptp2Line, bar_index) if not na(entryLine) line.set_x2(entryLine, bar_index) line.set_y1(entryLine, tEntry) line.set_y2(entryLine, tEntry) if not na(tpLine) line.set_x2(tpLine, bar_index) line.set_y1(tpLine, tTP) line.set_y2(tpLine, tTP) // Keep current-trade labels aligned to the right edge of the trade box int xr = not na(tradeBoxTP) ? box.get_right(tradeBoxTP) : bar_index if endLblOn if not na(tslCurLbl) and not na(tSL) label.set_x(tslCurLbl, xr) label.set_y(tslCurLbl, tSL) label.set_text(tslCurLbl, str.tostring(tSL, format.mintick)) if not na(ptp1CurLbl) and not na(ptp1Px) label.set_x(ptp1CurLbl, xr) if not na(ptp2CurLbl) and not na(ptp2Px) label.set_x(ptp2CurLbl, xr) if not na(entryCurLbl) and not na(tEntry) label.set_x(entryCurLbl, xr) label.set_y(entryCurLbl, tEntry) label.set_text(entryCurLbl, str.tostring(tEntry, format.mintick)) if not na(tpCurLbl) and not na(tTP) label.set_x(tpCurLbl, xr) label.set_y(tpCurLbl, tTP) label.set_text(tpCurLbl, str.tostring(tTP, format.mintick)) if inPos and not inPos[1] tEntryBar := bar_index bool isLong = strategy.position_size > 0 // Initialize TSL visuals (one trade at a time; finalized on trade close) if (tsl1On or tsl2On or tsl3On or tsl4On or tsl5On) and not na(tR0) and tR0 > 0 tslSegStart := bar_index tslSegPx := tSL tslSegLine := line.new(x1=bar_index, y1=tSL, x2=bar_index, y2=tSL, xloc=xloc.bar_index, extend=extend.none, color=cTsl, width=2) // Entry + TP lines entryLine := line.new(x1=bar_index, y1=tEntry, x2=bar_index, y2=tEntry, xloc=xloc.bar_index, extend=extend.none, color=cEntry, width=2) tpLine := line.new(x1=bar_index, y1=tTP, x2=bar_index, y2=tTP, xloc=xloc.bar_index, extend=extend.none, color=cTp, width=2) // Initialize PTP visuals (orange dotted, not dynamic) if not na(tR0) and tR0 > 0 if tp1On ptp1Px := isLong ? (tEntry + tR0 * tp1R) : (tEntry - tR0 * tp1R) ptp1Line := line.new(x1=bar_index, y1=ptp1Px, x2=bar_index, y2=ptp1Px, xloc=xloc.bar_index, extend=extend.none, color=cPtp, style=line.style_dotted, width=2) if tp2On ptp2Px := isLong ? (tEntry + tR0 * tp2R) : (tEntry - tR0 * tp2R) ptp2Line := line.new(x1=bar_index, y1=ptp2Px, x2=bar_index, y2=ptp2Px, xloc=xloc.bar_index, extend=extend.none, color=cPtp, style=line.style_dotted, width=2) // Create per-trade end labels (kept for last N trades) if endLblOn entryCurLbl := label.new(bar_index + 1, tEntry, str.tostring(tEntry, format.mintick), xloc=xloc.bar_index, style=label.style_label_left, size=(endLblSizeOpt == "Small" ? size.small : size.tiny), textcolor=cEntry, color=color.new(cEntry, 100)) f_pushLbl(entryLbls, entryCurLbl) tpCurLbl := label.new(bar_index + 1, tTP, str.tostring(tTP, format.mintick), xloc=xloc.bar_index, style=label.style_label_left, size=(endLblSizeOpt == "Small" ? size.small : size.tiny), textcolor=cTp, color=color.new(cTp, 100)) f_pushLbl(tpLbls, tpCurLbl) tslCurLbl := label.new(bar_index + 1, tSL, str.tostring(tSL, format.mintick), xloc=xloc.bar_index, style=label.style_label_left, size=(endLblSizeOpt == "Small" ? size.small : size.tiny), textcolor=cTsl, color=color.new(cTsl, 100)) f_pushLbl(tslLbls, tslCurLbl) if tp1On and not na(ptp1Px) ptp1CurLbl := label.new(bar_index + 1, ptp1Px, str.tostring(ptp1Px, format.mintick) + " | " + str.tostring(tp1Pct, "#") + "%", xloc=xloc.bar_index, style=label.style_label_left, size=(endLblSizeOpt == "Small" ? size.small : size.tiny), textcolor=cPtp, color=color.new(cPtp, 100)) f_pushLbl(ptp1Lbls, ptp1CurLbl) if tp2On and not na(ptp2Px) ptp2CurLbl := label.new(bar_index + 1, ptp2Px, str.tostring(ptp2Px, format.mintick) + " | " + str.tostring(tp2Pct, "#") + "%", xloc=xloc.bar_index, style=label.style_label_left, size=(endLblSizeOpt == "Small" ? size.small : size.tiny), textcolor=cPtp, color=color.new(cPtp, 100)) f_pushLbl(ptp2Lbls, ptp2CurLbl) // ---------- Exits (partials + hard TP) ---------- // Important: allow exits on the SAME bar the position opens. // Otherwise, a fast 1-bar move right after entry can skip all TP fills. bool allowExits = inPos and not na(tEntryBar) and bar_index >= tEntryBar and not afterTradeEnd if allowExits and not na(tEntry) and not na(tR0) and tR0 > 0 and not na(tTP) // --- Trailing stop (step-based, RR-triggered) --- if (tsl1On or tsl2On or tsl3On or tsl4On or tsl5On) bool isLong = strategy.position_size > 0 float bestStop = tSL // Trigger checks use initial risk (tR0) so RR levels remain stable. if isLong bool tr1 = tsl1On and high >= (tEntry + tR0 * tsl1TrigR) bool tr2 = tsl2On and high >= (tEntry + tR0 * tsl2TrigR) bool tr3 = tsl3On and high >= (tEntry + tR0 * tsl3TrigR) bool tr4 = tsl4On and high >= (tEntry + tR0 * tsl4TrigR) bool tr5 = tsl5On and high >= (tEntry + tR0 * tsl5TrigR) if tr1 bestStop := math.max(bestStop, tEntry + tR0 * tsl1ToR) if tr2 bestStop := math.max(bestStop, tEntry + tR0 * tsl2ToR) if tr3 bestStop := math.max(bestStop, tEntry + tR0 * tsl3ToR) if tr4 bestStop := math.max(bestStop, tEntry + tR0 * tsl4ToR) if tr5 bestStop := math.max(bestStop, tEntry + tR0 * tsl5ToR) else bool tr1 = tsl1On and low <= (tEntry - tR0 * tsl1TrigR) bool tr2 = tsl2On and low <= (tEntry - tR0 * tsl2TrigR) bool tr3 = tsl3On and low <= (tEntry - tR0 * tsl3TrigR) bool tr4 = tsl4On and low <= (tEntry - tR0 * tsl4TrigR) bool tr5 = tsl5On and low <= (tEntry - tR0 * tsl5TrigR) if tr1 bestStop := math.min(bestStop, tEntry - tR0 * tsl1ToR) if tr2 bestStop := math.min(bestStop, tEntry - tR0 * tsl2ToR) if tr3 bestStop := math.min(bestStop, tEntry - tR0 * tsl3ToR) if tr4 bestStop := math.min(bestStop, tEntry - tR0 * tsl4ToR) if tr5 bestStop := math.min(bestStop, tEntry - tR0 * tsl5ToR) // Only trail in a favorable direction float newStop = isLong ? math.max(tSL, bestStop) : math.min(tSL, bestStop) tSL := newStop // Update the step-line segment (restore original "perfect" step format) float eps = syminfo.mintick / 10.0 if not na(tslSegLine) line.set_x2(tslSegLine, bar_index) if not na(tslSegPx) and math.abs(tSL - tslSegPx) > eps int endX = math.max(tslSegStart, bar_index - 1) line.set_x2(tslSegLine, endX) tslSegStart := bar_index tslSegPx := tSL tslSegLine := line.new(x1=bar_index, y1=tSL, x2=bar_index, y2=tSL, xloc=xloc.bar_index, extend=extend.none, color=cTsl, width=2) float stopPx = tSL if strategy.position_size > 0 // Single SL implemented as a stop order. // Using strategy.order here avoids the "TP exits never fill" issue that can happen when combining // multiple strategy.exit() orders (partials) with a full-position stop exit. strategy.cancel("L_SL") strategy.order("L_SL", strategy.short, qty=math.abs(strategy.position_size), stop=stopPx, comment="SL") // Keep TP orders working; do NOT cancel them based on same-bar OHLC touches. if tp1On strategy.exit("L_TP1", "L", limit=tEntry + tR0 * tp1R, qty_percent=tp1Pct, comment_profit="Ptp") if tp2On strategy.exit("L_TP2", "L", limit=tEntry + tR0 * tp2R, qty_percent=tp2Pct, comment_profit="Ptp") // Hard TP closes whatever is left strategy.exit("L_TP", "L", limit=tTP, comment_profit="Ltp") else // Single SL implemented as a stop order. strategy.cancel("S_SL") strategy.order("S_SL", strategy.long, qty=math.abs(strategy.position_size), stop=stopPx, comment="SL") // Keep TP orders working; do NOT cancel them based on same-bar OHLC touches. if tp1On strategy.exit("S_TP1", "S", limit=tEntry - tR0 * tp1R, qty_percent=tp1Pct, comment_profit="Ptp") if tp2On strategy.exit("S_TP2", "S", limit=tEntry - tR0 * tp2R, qty_percent=tp2Pct, comment_profit="Ptp") // Hard TP closes whatever is left strategy.exit("S_TP", "S", limit=tTP, comment_profit="Stp") // Flat at/after trade end NY (also prevents late fills) if tradeEndJustHit if inPos strategy.close_all(comment="Trade end close") // Cancel all pending orders (entries + exits), otherwise stops can trigger after trade end as new positions. strategy.cancel("L") strategy.cancel("S") strategy.cancel("L_SL") strategy.cancel("S_SL") strategy.cancel("L_TP1") strategy.cancel("L_TP2") strategy.cancel("L_TP") strategy.cancel("S_TP1") strategy.cancel("S_TP2") strategy.cancel("S_TP") lastEntryTime5 := na slLock := false // Safety: after trade end, keep exits canceled so nothing can re-open/modify positions. if afterTradeEnd strategy.cancel("L_SL") strategy.cancel("S_SL") strategy.cancel("L_TP1") strategy.cancel("L_TP2") strategy.cancel("L_TP") strategy.cancel("S_TP1") strategy.cancel("S_TP2") strategy.cancel("S_TP")