//@version=5 strategy("BTC Trend Breakout — Risk Managed v5", overlay = true, initial_capital = 10000, currency = currency.USD, pyramiding = 0, commission_type = strategy.commission.percent, commission_value = 0.10, slippage = 10, margin_long = 100, margin_short = 100, calc_on_every_tick = false, calc_on_order_fills = false, process_orders_on_close = false) // Designed for BTC pairs quoted in USD or USDT. // Start testing on standard 4-hour candles. // Commission: 0.10% per fill. Slippage: 10 symbol ticks. // Adjust both in Strategy Properties for your venue. // ── Inputs ─────────────────────────────────────────── trendLength = input.int(200, "Trend EMA length", minval = 2) slopeBars = input.int(10, "EMA slope lookback", minval = 1) breakoutLength = input.int(55, "Breakout lookback", minval = 2) exitLength = input.int(20, "Channel exit lookback", minval = 2) atrLength = input.int(14, "ATR length", minval = 1) stopATR = input.float(2.5, "Initial stop: ATR multiple", minval = 0.5, step = 0.1) rewardRisk = input.float(3.0, "Profit target: reward/risk", minval = 0.5, step = 0.25) riskPercent = input.float(0.5, "Equity risk per trade (%)", minval = 0.05, maxval = 5.0, step = 0.05) exposurePct = input.float(95.0, "Maximum equity exposure (%)", minval = 1.0, maxval = 100.0, step = 1.0) enableShorts = input.bool(false, "Enable shorts") useChannelExit = input.bool(true, "Enable channel exits") startDate = input.time(timestamp("01 Jan 2020 00:00 +0000"), "Backtest start") endDate = input.time(timestamp("01 Jan 2035 00:00 +0000"), "Backtest end") // ── Indicators ─────────────────────────────────────── trendEMA = ta.ema(close, trendLength) atrValue = ta.atr(atrLength) // Exclude the current candle from all channel calculations. upperChannel = ta.highest(high, breakoutLength)[1] lowerChannel = ta.lowest(low, breakoutLength)[1] longExitLine = ta.lowest(low, exitLength)[1] shortExitLine = ta.highest(high, exitLength)[1] ready = not na(trendEMA[slopeBars]) and not na(upperChannel) and not na(lowerChannel) and not na(atrValue) inWindow = time >= startDate and time_close < endDate bullTrend = close > trendEMA and trendEMA > trendEMA[slopeBars] bearTrend = close < trendEMA and trendEMA < trendEMA[slopeBars] longBreakout = ta.crossover(close, upperChannel) shortBreakout = ta.crossunder(close, lowerChannel) // ── Position sizing ────────────────────────────────── // Intended stop risk excludes fees, gaps, and excess slippage. // The exposure cap is estimated using the signal-bar close. stopTicks = math.max(1.0, math.ceil(atrValue * stopATR / syminfo.mintick)) targetTicks = math.max(1.0, math.round(stopTicks * rewardRisk)) stopDistance = stopTicks * syminfo.mintick pointValue = syminfo.pointvalue riskCash = math.max(strategy.equity, 0.0) * riskPercent / 100.0 exposureCash = math.max(strategy.equity, 0.0) * exposurePct / 100.0 riskQty = riskCash / (stopDistance * pointValue) exposureQty = exposureCash / (close * pointValue) orderQty = math.min(riskQty, exposureQty) // Freeze exit distances when an entry order is submitted. // Relative exit distances are measured from the actual fill. var float activeStopTicks = na var float activeTargetTicks = na if strategy.position_size == 0 activeStopTicks := na activeTargetTicks := na canEnter = barstate.isconfirmed and ready and inWindow and strategy.position_size == 0 and not na(orderQty) and orderQty > 0 if canEnter if bullTrend and longBreakout activeStopTicks := stopTicks activeTargetTicks := targetTicks strategy.entry("Long", strategy.long, qty = orderQty) strategy.exit("Long bracket", from_entry = "Long", loss = activeStopTicks, profit = activeTargetTicks) else if enableShorts and bearTrend and shortBreakout activeStopTicks := stopTicks activeTargetTicks := targetTicks strategy.entry("Short", strategy.short, qty = orderQty) strategy.exit("Short bracket", from_entry = "Short", loss = activeStopTicks, profit = activeTargetTicks) // ── Manage open positions ──────────────────────────── if strategy.position_size > 0 strategy.exit("Long bracket", from_entry = "Long", loss = activeStopTicks, profit = activeTargetTicks) if barstate.isconfirmed and useChannelExit and close < longExitLine strategy.close("Long", comment = "Channel exit") if strategy.position_size < 0 strategy.exit("Short bracket", from_entry = "Short", loss = activeStopTicks, profit = activeTargetTicks) if barstate.isconfirmed and useChannelExit and close > shortExitLine strategy.close("Short", comment = "Channel exit") // Close at the end of the selected test window. if barstate.isconfirmed and time_close >= endDate strategy.cancel_all() strategy.close_all(comment = "Backtest end") // ── Chart display ──────────────────────────────────── float stopPrice = na float targetPrice = na if strategy.position_size > 0 stopPrice := strategy.position_avg_price - activeStopTicks * syminfo.mintick targetPrice := strategy.position_avg_price + activeTargetTicks * syminfo.mintick if strategy.position_size < 0 stopPrice := strategy.position_avg_price + activeStopTicks * syminfo.mintick targetPrice := strategy.position_avg_price - activeTargetTicks * syminfo.mintick plot(trendEMA, "Trend EMA", color = color.orange, linewidth = 2) plot(upperChannel, "Breakout high", color = color.new(color.green, 65)) plot(lowerChannel, "Breakout low", color = color.new(color.red, 65)) plot(stopPrice, "Stop", color = color.red, style = plot.style_linebr) plot(targetPrice, "Target", color = color.lime, style = plot.style_linebr)