//@version=5 // SA_EXPORT|format=sa_tv_exact_v1|semantics=tv_exact_v1|fill=signal_close|intrabar_path=tv_ohlc_path|reentry=cancel_first|export_id=sa-98bac5d44d050bf0 // Strategy Arena Lab export, executionSemantics = tv_exact_v1 (TradingView-exact semantics of the Lab CPU reference). // Fills at the signal bar close (process_orders_on_close = true); quantity = cash x size / (close x (1 + commission)), rounded down to saQtyStep when set (TradingView lot step). // stop_loss / take_profit: one strategy.exit(stop, limit) placed at entry, filled intrabar on high/low, at the open on a gap; levels on the tick grid away from the entry (TradingView rule). // A close decided on a bar close cancels its bracket first, so an entry on the same close fills exactly like the Lab (TradingView probe, 8 Oct 2026). Pine Logs report any gap between the Lab mirror and the TradingView position as SATRADES1|DESYNC. // Stop and target inside the same bar: TradingView open/high/low path, as in the Lab run with intrabarPath = tv_ohlc_path. // Trailing stop, timeout, exit conditions and implicit signal exits: decided on each close in the Lab order, filled at that close. // Indicators: TradingView formulas anchored at the first bar of the Lab window (saFromTime), so chart history before it does not change them. // Warmup: no signal before window bar saFirstTradable (every indicator defined). Commission in percent of the traded value per side; slippage in ticks on market and stop fills (TradingView model). // Optional trade log: each closed trade is written to Pine Logs (SATRADES1 lines); copy the Pine Logs panel into the Lab to compare trade by trade. // Descriptive sandbox configuration, not a sealed proof. Parameters are fixed at the exported values. strategy("V5 Zarattini Donchian Ensemble", overlay=true, initial_capital=10000.0, default_qty_type=strategy.fixed, default_qty_value=1, commission_type=strategy.commission.percent, commission_value=0.1, slippage=0, process_orders_on_close=true, calc_on_order_fills=false, calc_on_every_tick=false, pyramiding=0, margin_long=0, margin_short=0) // ---- Lab configuration (fixed at the exported values) ---- int saFromTime = 0 int saToTime = 0 string saExportId = "sa-98bac5d44d050bf0" string saScriptName = "V5 Zarattini Donchian Ensemble" string saIntrabarPath = "tv_ohlc_path" string saTieBreak = "low_first" string saReentryOrder = "cancel_first" float saQtyStep = 0.0 int saQtyDecimals = 0 float saCapital = 10000.0 float saPositionSize = 1.0 float saFee = 0.001 int saSlippageTicks = 0 float saTakeProfit = na float saStopLoss = na int saTimeout = na float saTrailing = na int saCooldown = 0 float saStopAtrMult = na float saTakeAtrMult = na float saShortInitialMargin = na float saShortMaintenance = 0.0 int saFirstTradable = 360 bool saImplicitSignalExits = true bool saLetRun = true bool saHasShortEntry = false float saRisk = na float saBeAt = na float saBeOff = 0.0 float saTrailMult = na float saTrailAfterAtr = na float saTrailAfterPct = na float saGivePct = na float saGiveAfter = na bool saTrailByLevel = false bool saVolOn = true bool saLogTrades = input.bool(true, "Write each closed trade to Pine Logs (Strategy Arena trade check)") float saSlip = saSlippageTicks * syminfo.mintick bool saInWindow = time >= saFromTime and (saToTime == 0 or time <= saToTime) var int saN = -1 if saInWindow saN += 1 // ---- Indicators anchored at the first Lab bar (saN = window bar index) ---- saGateA(float v, int first) => saN >= first ? v : na saSmaA(float src, simple int n) => float v = ta.sma(src, n) saN >= n - 1 ? v : na // ta.ema / ta.rma recursion: SMA seed at window bar `first`, then alpha * src + (1 - alpha) * previous. saRecA(float src, simple int n, float alpha, int first) => float seed = ta.sma(src, n) var float r = na if saN >= first r := na(r) ? seed : alpha * src + (1.0 - alpha) * r r saRsiA(simple int n) => float u = saN >= 1 ? math.max(close - close[1], 0.0) : na float d = saN >= 1 ? math.max(close[1] - close, 0.0) : na float up = saRecA(u, n, 1.0 / n, n) float dn = saRecA(d, n, 1.0 / n, n) na(up) or na(dn) ? na : dn == 0 ? 100.0 : up == 0 ? 0.0 : 100.0 - 100.0 / (1.0 + up / dn) saAtrA(simple int n) => float tr = saN < 0 ? na : saN == 0 ? high - low : math.max(math.max(high - low, math.abs(high - close[1])), math.abs(low - close[1])) saRecA(tr, n, 1.0 / n, n - 1) // Comparisons with na are false, as in the Lab (no signal during the warmup). saGt(float a, float b) => not na(a) and not na(b) and a > b saLt(float a, float b) => not na(a) and not na(b) and a < b saXUp(float a, float b) => not na(a) and not na(b) and not na(a[1]) and not na(b[1]) and a > b and a[1] <= b[1] saXDn(float a, float b) => not na(a) and not na(b) and not na(a[1]) and not na(b[1]) and a < b and a[1] >= b[1] // Lab qtyStep: quantity rounded down to the lot step (identity when saQtyStep = 0). saRoundQty(float q) => saQtyStep > 0 ? math.round(math.floor(q / saQtyStep + 0.000000001) * saQtyStep, saQtyDecimals) : q saNum(float v) => str.tostring(v, "#.##########") // Lab 236: stop and limit levels on the tick grid, AWAY from the entry (the TradingView rule): // long stop / short target down, long target / short stop up. saTickDown(float v) => na(v) ? na : math.round_to_mintick(math.floor(v / syminfo.mintick + 0.0000001) * syminfo.mintick) saTickUp(float v) => na(v) ? na : math.round_to_mintick(math.ceil(v / syminfo.mintick - 0.0000001) * syminfo.mintick) // ---- wave-243c helpers (expression atoms, higher timeframe) ---- saGe(float a, float b) => not na(a) and not na(b) and a >= b saLe(float a, float b) => not na(a) and not na(b) and a <= b saEq(float a, float b) => not na(a) and not na(b) and a == b saNe(float a, float b) => not na(a) and not na(b) and a != b // ---- wave-244b helpers (VOTE, max/min, month/week end, Donchian trend, vol target) ---- saB01(bool b) => b ? 1.0 : 0.0 saMaxA(float a, float b) => na(a) or na(b) ? na : math.max(a, b) saMinA(float a, float b) => na(a) or na(b) ? na : math.min(a, b) saMonthEnd() => month(time_close, "UTC") != month(time, "UTC") or year(time_close, "UTC") != year(time, "UTC") ? 1.0 : 0.0 saWeekEnd() => math.floor((time_close - 345600000) / 604800000) != math.floor((time - 345600000) / 604800000) ? 1.0 : 0.0 saDtA(simple int n) => float hh = saGateA(ta.highest(high, n)[1], n) float ll = saGateA(ta.lowest(low, n)[1], n) var float st = 0.0 if not na(hh) and not na(ll) st := close > hh ? 1.0 : close < ll ? 0.0 : st na(hh) or na(ll) ? na : st saVolFracA(simple int w, float tgt, float cap, float bpy) => float f = na if saN >= w float sum = 0.0 for k = w - 1 to 0 sum += close[k] / close[k + 1] - 1.0 float mean = sum / w float q = 0.0 for k = w - 1 to 0 float e = (close[k] / close[k + 1] - 1.0) - mean q += e * e float sig = math.sqrt(q / w) * math.sqrt(bpy) if sig > 0 f := math.min(tgt / 100.0 / sig, cap / 100.0) f // ---- Series (computed on every bar) ---- float saE0 = saDtA(5) // 243c: donchian_trend(5) float saE1 = saB01(saGt(saE0, 0.0)) // 243c: vote arm donchian_trend(5) > 0 float saE2 = saDtA(10) // 243c: donchian_trend(10) float saE3 = saB01(saGt(saE2, 0.0)) // 243c: vote arm donchian_trend(10) > 0 float saE4 = saDtA(20) // 243c: donchian_trend(20) float saE5 = saB01(saGt(saE4, 0.0)) // 243c: vote arm donchian_trend(20) > 0 float saE6 = saDtA(30) // 243c: donchian_trend(30) float saE7 = saB01(saGt(saE6, 0.0)) // 243c: vote arm donchian_trend(30) > 0 float saE8 = saDtA(60) // 243c: donchian_trend(60) float saE9 = saB01(saGt(saE8, 0.0)) // 243c: vote arm donchian_trend(60) > 0 float saE10 = saDtA(90) // 243c: donchian_trend(90) float saE11 = saB01(saGt(saE10, 0.0)) // 243c: vote arm donchian_trend(90) > 0 float saE12 = saDtA(150) // 243c: donchian_trend(150) float saE13 = saB01(saGt(saE12, 0.0)) // 243c: vote arm donchian_trend(150) > 0 float saE14 = saDtA(250) // 243c: donchian_trend(250) float saE15 = saB01(saGt(saE14, 0.0)) // 243c: vote arm donchian_trend(250) > 0 float saE16 = saDtA(360) // 243c: donchian_trend(360) float saE17 = saB01(saGt(saE16, 0.0)) // 243c: vote arm donchian_trend(360) > 0 float saE18 = (saE1 + saE3 + saE5 + saE7 + saE9 + saE11 + saE13 + saE15 + saE17) // 243c: vote(donchian_trend(5) > 0, donchian_trend(10) > 0, donchian_trend(20) > 0, donchian_trend(30) > 0, donchian_trend(60) > 0, donchian_trend(90) > 0, donchian_trend(150) > 0, donchian_trend(250) > 0, donchian_trend(360) > 0) float saE19 = saVolFracA(90, 25.0, 100.0, 365.0) // 243c: vol_target 25% window 90 cap 100% float saAtrStop = na float saAtrTake = na float saAtrTrail = na float saLevelNow = na float saTrailLevelNow = na float saVolNow = saE19 saRiskFrac(bool lg) => float ref = lg ? close + saSlip : close - saSlip float lvl = na if not na(saStopLoss) lvl := lg ? ref * (1.0 - saStopLoss / 100.0) : ref * (1.0 + saStopLoss / 100.0) if not na(saStopAtrMult) and saStopAtrMult > 0 and not na(saAtrStop) and saAtrStop > 0.000000000001 float l2 = lg ? ref - saStopAtrMult * saAtrStop : ref + saStopAtrMult * saAtrStop lvl := na(lvl) ? l2 : lg ? math.max(lvl, l2) : math.min(lvl, l2) if not na(saLevelNow) and (lg ? saLevelNow < ref : saLevelNow > ref) lvl := na(lvl) ? saLevelNow : lg ? math.max(lvl, saLevelNow) : math.min(lvl, saLevelNow) float dist = na(lvl) ? na : lg ? ref - lvl : lvl - ref saVolOn ? (na(saVolNow) or not (saVolNow > 0) ? na : saVolNow) : na(saRisk) ? saPositionSize : na(dist) or not (dist > 0) ? na : math.min(saRisk / 100.0 * ref * (lg ? 1.0 + saFee : 1.0 - saFee) / dist, 1.0) bool saA0 = saGe(saE18, 5.0) // 243c: vote(donchian_trend(5) > 0, donchian_trend(10) > 0, donchian_trend(20) > 0, donchian_trend(30) > 0, donchian_trend(60) > 0, donchian_trend(90) > 0, donchian_trend(150) > 0, donchian_trend(250) > 0, donchian_trend(360) > 0) >= 5 bool saLongEntry = saA0 bool saShortEntry = false bool saLongExit = false bool saShortExit = false bool saLongInverseExit = false // ---- Lab CPU reference state (run_reference_with_policy, tv_exact_v1, signal_close) ---- var float saCash = saCapital var float saQty = 0.0 var float saEntryRef = na var float saEntryFill = na var int saEntryBar = na var float saPeak = 0.0 var float saTrough = 0.0 var int saCooldownLeft = 0 var string saEntryId = "" var bool saWindowClosed = false var int saLogged = 0 var bool saSummaryDone = false var int saDesyncLogged = 0 var bool saBeArmed = false var bool saTrailOn = false var float saTrailLvl = na var float saLevelStop = na var float saStopRaw = na var float saTargetRaw = na var float saLiqLvl = na var float saTroughX = 0.0 // ---- Trade log (SATRADES1, read by the Strategy Arena Lab trade comparator) ---- if saLogTrades if barstate.isfirst log.info("SATRADES1|META|export_id=" + saExportId + "|format=1|semantics=tv_exact_v1|fill=signal_close|intrabar_path=" + saIntrabarPath + "|tickerid=" + syminfo.tickerid + "|timeframe=" + timeframe.period + "|chart_first_time=" + str.tostring(time) + "|from_time=" + str.tostring(saFromTime) + "|to_time=" + str.tostring(saToTime) + "|capital=" + saNum(saCapital) + "|fee_bps=" + saNum(saFee * 10000.0) + "|slippage_ticks=" + str.tostring(saSlippageTicks) + "|mintick=" + saNum(syminfo.mintick) + "|intrabar_tie_break=" + saTieBreak + "|qty_step=" + saNum(saQtyStep) + "|reentry=" + saReentryOrder + "|first_tradable=" + str.tostring(saFirstTradable) + "|script=" + saScriptName) while saLogged < strategy.closedtrades int k = saLogged float sz = strategy.closedtrades.size(k) log.info("SATRADES1|TRADE|export_id=" + saExportId + "|n=" + str.tostring(k + 1) + "|side=" + (sz > 0 ? "long" : "short") + "|entry_time=" + str.tostring(strategy.closedtrades.entry_time(k)) + "|entry_price=" + saNum(strategy.closedtrades.entry_price(k)) + "|exit_time=" + str.tostring(strategy.closedtrades.exit_time(k)) + "|exit_price=" + saNum(strategy.closedtrades.exit_price(k)) + "|qty=" + saNum(math.abs(sz)) + "|profit=" + saNum(strategy.closedtrades.profit(k)) + "|commission=" + saNum(strategy.closedtrades.commission(k)) + "|entry_id=" + strategy.closedtrades.entry_id(k) + "|exit_id=" + strategy.closedtrades.exit_id(k)) saLogged += 1 if not saSummaryDone and ((saToTime != 0 and time > saToTime) or barstate.islastconfirmedhistory) if strategy.opentrades > 0 for j = 0 to strategy.opentrades - 1 float osz = strategy.opentrades.size(j) log.info("SATRADES1|OPEN|export_id=" + saExportId + "|n=" + str.tostring(strategy.closedtrades + j + 1) + "|side=" + (osz > 0 ? "long" : "short") + "|entry_time=" + str.tostring(strategy.opentrades.entry_time(j)) + "|entry_price=" + saNum(strategy.opentrades.entry_price(j)) + "|qty=" + saNum(math.abs(osz)) + "|entry_id=" + strategy.opentrades.entry_id(j)) log.info("SATRADES1|SUMMARY|export_id=" + saExportId + "|closed=" + str.tostring(strategy.closedtrades) + "|open=" + str.tostring(strategy.opentrades) + "|net_profit=" + saNum(strategy.netprofit) + "|last_time=" + str.tostring(time)) saSummaryDone := true // After the Lab window: no new order; an open position stays open (the Lab closes it at its last bar). if saToTime != 0 and time > saToTime and not saWindowClosed strategy.cancel_all() saWindowClosed := true if saInWindow bool saCooling = saCooldownLeft > 0 if saCooling saCooldownLeft -= 1 // Lab 236 DESYNC guard: the Lab mirror must hold the position TradingView holds; never a silent gap. if saLogTrades and saDesyncLogged < 20 float saTvQty = strategy.position_size float saQtyTol = saQtyStep > 0 ? saQtyStep * 0.5 : math.abs(saQty) * 0.001 string saDesync = "" if saQty != 0 and saTvQty != 0 and (math.sign(saTvQty) != math.sign(saQty) or math.abs(math.abs(saTvQty) - math.abs(saQty)) > saQtyTol) saDesync := "size_differs" else if saQty != 0 and saTvQty == 0 and (strategy.closedtrades == 0 or strategy.closedtrades.entry_id(strategy.closedtrades - 1) != saEntryId) saDesync := "entry_not_filled" else if saQty == 0 and saTvQty != 0 saDesync := "unexpected_position" if saDesync != "" log.warning("SATRADES1|DESYNC|export_id=" + saExportId + "|time=" + str.tostring(time) + "|reason=" + saDesync + "|lab_qty=" + saNum(saQty) + "|tv_qty=" + saNum(saTvQty) + "|entry_id=" + saEntryId) saDesyncLogged += 1 // Intrabar stop / target / liquidation filled by strategy.exit since the last close. if saQty != 0 and strategy.position_size == 0 and strategy.closedtrades > 0 float saExitPx = strategy.closedtrades.exit_price(strategy.closedtrades - 1) saCash := math.max(saCash + saQty * (saQty > 0 ? saExitPx * (1.0 - saFee) : saExitPx * (1.0 + saFee)), 0.0) saQty := 0.0 saEntryRef := na saEntryFill := na saEntryBar := na saPeak := 0.0 saTrough := 0.0 saCooldownLeft := saCooldown saBeArmed := false saTrailOn := false saTrailLvl := na saLevelStop := na saStopRaw := na saTargetRaw := na saLiqLvl := na // Exits decided on this close, in the Lab order. if saQty != 0 bool saIsLong = saQty > 0 float saPnlPct = saIsLong ? (close - saEntryFill) / saEntryFill * 100.0 : (saEntryFill - close) / saEntryFill * 100.0 if saIsLong if saPeak <= 0 or close > saPeak saPeak := close else if saTrough <= 0 or close < saTrough saTrough := close if not saIsLong and (saTroughX <= 0 or close < saTroughX) saTroughX := close float saBest = saIsLong ? (saPeak - saEntryFill) / saEntryFill * 100.0 : (saEntryFill - saTroughX) / saEntryFill * 100.0 bool saGiveHit = not na(saGivePct) and saBest > 0 and saBest >= saGiveAfter and saPnlPct <= saBest * (1.0 - saGivePct / 100.0) bool saTrailHit = not na(saTrailing) and saTrailing > 0 and (saIsLong ? (saPeak > 0 and saPnlPct > 0 and (saPeak - close) / saPeak * 100.0 >= saTrailing) : (saTrough > 0 and saPnlPct > 0 and (close - saTrough) / saTrough * 100.0 >= saTrailing)) string saReason = "" if saTrailHit saReason := "trailing_stop" else if saGiveHit saReason := "giveback" else if not na(saTimeout) and saTimeout > 0 and bar_index - saEntryBar >= saTimeout saReason := "timeout" else if saIsLong ? saLongExit : saShortExit saReason := "exit_condition" else if saIsLong and saImplicitSignalExits and saLongInverseExit saReason := "signal" else if saIsLong and saImplicitSignalExits and saLetRun and not saLongEntry saReason := "signal" else if not saIsLong and saImplicitSignalExits and saHasShortEntry and not saShortEntry saReason := "signal" if saReason != "" // Lab 236: cancel the bracket BEFORE the close. With an active strategy.exit, TradingView does not make // strategy.close effective when it is placed: a same-bar entry would be refused (same side) or doubled (reversal). strategy.cancel("X" + saEntryId) strategy.close(saEntryId, comment=saReason, immediately=true) saCash := math.max(saCash + saQty * (saIsLong ? (close - saSlip) * (1.0 - saFee) : (close + saSlip) * (1.0 + saFee)), 0.0) saQty := 0.0 saEntryRef := na saEntryFill := na saEntryBar := na saPeak := 0.0 saTrough := 0.0 saCooldownLeft := saCooldown saBeArmed := false saTrailOn := false saTrailLvl := na saLevelStop := na saStopRaw := na saTargetRaw := na saLiqLvl := na // Entries on this close (long first). bool saMayEnter = saN >= saFirstTradable and saQty == 0 and saCash > 0 and not saCooling float saFracL = saMayEnter ? saRiskFrac(true) : na float saFracS = saMayEnter ? saRiskFrac(false) : na if saMayEnter and saLongEntry and not na(saFracL) float saSpend = saCash * saFracL saEntryRef := close + saSlip saEntryFill := saEntryRef * (1.0 + saFee) saQty := saRoundQty(saSpend / saEntryFill) saCash := saCash - saQty * saEntryFill saEntryBar := bar_index saPeak := close saTrough := 0.0 saEntryId := "L" + str.tostring(bar_index) strategy.entry(saEntryId, strategy.long, qty=saQty) float saStopPx = na if not na(saStopLoss) saStopPx := saEntryRef * (1.0 - saStopLoss / 100.0) if not na(saStopAtrMult) and saStopAtrMult > 0 and not na(saAtrStop) and saAtrStop > 0.000000000001 float lvl = saEntryRef - saStopAtrMult * saAtrStop saStopPx := na(saStopPx) ? lvl : math.max(saStopPx, lvl) float saTargetPx = na if not na(saTakeProfit) saTargetPx := saEntryRef * (1.0 + saTakeProfit / 100.0) if not na(saTakeAtrMult) and saTakeAtrMult > 0 and not na(saAtrTake) and saAtrTake > 0.000000000001 float lvl = saEntryRef + saTakeAtrMult * saAtrTake saTargetPx := na(saTargetPx) ? lvl : math.min(saTargetPx, lvl) saLevelStop := not na(saLevelNow) and saLevelNow < saEntryRef ? saLevelNow : na if not na(saLevelStop) saStopPx := na(saStopPx) ? saLevelStop : math.max(saStopPx, saLevelStop) saStopRaw := saStopPx saTargetRaw := saTargetPx saBeArmed := false saTrailOn := false saTrailLvl := na saStopPx := saTickDown(saStopPx) saTargetPx := saTickUp(saTargetPx) if not na(saStopPx) or not na(saTargetPx) strategy.exit("X" + saEntryId, from_entry=saEntryId, stop=saStopPx, limit=saTargetPx) else if saMayEnter and not saLongEntry and saHasShortEntry and saShortEntry and not na(saFracS) float saNotional = saCash * saFracS if not na(saShortInitialMargin) saNotional := math.min(saNotional, saCash / (saShortInitialMargin / 100.0)) saEntryRef := close - saSlip saEntryFill := saEntryRef * (1.0 - saFee) saQty := -saRoundQty(saNotional / saEntryFill) saCash := saCash + (-saQty) * saEntryFill saEntryBar := bar_index saTrough := saEntryFill saTroughX := saEntryRef saPeak := 0.0 saEntryId := "S" + str.tostring(bar_index) strategy.entry(saEntryId, strategy.short, qty=-saQty) float saStopPx = na if not na(saStopLoss) saStopPx := saEntryRef * (1.0 + saStopLoss / 100.0) if not na(saStopAtrMult) and saStopAtrMult > 0 and not na(saAtrStop) and saAtrStop > 0.000000000001 float lvl = saEntryRef + saStopAtrMult * saAtrStop saStopPx := na(saStopPx) ? lvl : math.min(saStopPx, lvl) saLevelStop := not na(saLevelNow) and saLevelNow > saEntryRef ? saLevelNow : na if not na(saLevelStop) saStopPx := na(saStopPx) ? saLevelStop : math.min(saStopPx, saLevelStop) saStopRaw := saStopPx saBeArmed := false saTrailOn := false saTrailLvl := na // Lab equity floor / maintenance margin: liquidation level, used as the stop when tighter. float saLiq = saSlip > 0 ? (saCash / (-saQty) - saSlip * (1.0 + saFee)) / ((1.0 + saFee) + saShortMaintenance / 100.0) : saCash / ((-saQty) * ((1.0 + saFee) + saShortMaintenance / 100.0)) saLiqLvl := saLiq saStopPx := saTickUp(na(saStopPx) ? saLiq : saLiq < saStopPx ? saLiq : saStopPx) float saTargetPx = na if not na(saTakeProfit) saTargetPx := saEntryRef * (1.0 - saTakeProfit / 100.0) if not na(saTakeAtrMult) and saTakeAtrMult > 0 and not na(saAtrTake) and saAtrTake > 0.000000000001 float lvl = saEntryRef - saTakeAtrMult * saAtrTake saTargetPx := na(saTargetPx) ? lvl : math.max(saTargetPx, lvl) saTargetRaw := saTargetPx saTargetPx := saTickDown(saTargetPx) strategy.exit("X" + saEntryId, from_entry=saEntryId, stop=saStopPx, limit=saTargetPx) // wave-243c: break-even arming and ATR trailing level on this close (the stop of the next bar). if saQty != 0 and (not na(saBeAt) or not na(saTrailMult) or saTrailByLevel) bool saLg = saQty > 0 float saPnlNow = saLg ? (close - saEntryFill) / saEntryFill * 100.0 : (saEntryFill - close) / saEntryFill * 100.0 if not na(saBeAt) and not saBeArmed and saPnlNow >= saBeAt saBeArmed := true if not na(saTrailMult) and not na(saAtrTrail) if not saTrailOn saTrailOn := not na(saTrailAfterAtr) ? (saLg ? close - saEntryRef >= saTrailAfterAtr * saAtrTrail : saEntryRef - close >= saTrailAfterAtr * saAtrTrail) : not na(saTrailAfterPct) ? saPnlNow >= saTrailAfterPct : true if saTrailOn float saCand = saLg ? saPeak - saTrailMult * saAtrTrail : saTroughX + saTrailMult * saAtrTrail saTrailLvl := na(saTrailLvl) ? saCand : saLg ? math.max(saTrailLvl, saCand) : math.min(saTrailLvl, saCand) if saTrailByLevel and not na(saTrailLevelNow) if not saTrailOn saTrailOn := not na(saTrailAfterPct) ? saPnlNow >= saTrailAfterPct : true if saTrailOn saTrailLvl := na(saTrailLvl) ? saTrailLevelNow : saLg ? math.max(saTrailLvl, saTrailLevelNow) : math.min(saTrailLvl, saTrailLevelNow) float saDyn = saStopRaw if saBeArmed float saBe = saLg ? saEntryRef * (1.0 + saBeOff / 100.0) : saEntryRef * (1.0 - saBeOff / 100.0) saDyn := na(saDyn) ? saBe : saLg ? math.max(saDyn, saBe) : math.min(saDyn, saBe) if not na(saTrailLvl) saDyn := na(saDyn) ? saTrailLvl : saLg ? math.max(saDyn, saTrailLvl) : math.min(saDyn, saTrailLvl) if not saLg and not na(saLiqLvl) saDyn := na(saDyn) ? saLiqLvl : math.min(saDyn, saLiqLvl) float saDynPx = saLg ? saTickDown(saDyn) : saTickUp(saDyn) float saTgtPx = saLg ? saTickUp(saTargetRaw) : saTickDown(saTargetRaw) if not na(saDynPx) or not na(saTgtPx) strategy.exit("X" + saEntryId, from_entry=saEntryId, stop=saDynPx, limit=saTgtPx)