Skip to content

How to exit unprofitable trades with a percentage-based stop loss in TradingView?

How to exit unprofitable trades with a percentage-based stop loss in TradingView?

Section titled “How to exit unprofitable trades with a percentage-based stop loss in TradingView?”

TL;DR
Turn your max-loss percentage into an absolute stop price and feed it to strategy.exit() so every open position closes once it loses the configured percent.

DifficultyIntermediate
Time to implement15-20 min
CategoryRisk Management
//@version=5
strategy("Percent stop demo", overlay=true)
maxLossPercent = input.float(2.0, "Stop loss (%)", step=0.1) * 0.01
if ta.crossover(close, ta.ema(close, 50))
strategy.entry("Long", strategy.long)
if strategy.position_size > 0
stopPrice = strategy.position_avg_price * (1 - maxLossPercent)
strategy.exit("Long %SL", from_entry="Long", stop=stopPrice)
Tip. Convert percentages to decimals once (`2% → 0.02`). Reuse that value so the same logic works for long, short, and trailing adjustments.

Percentage stops scale with trade price so you risk the same fraction of capital regardless of the symbol or timeframe. They protect strategies from sudden swings while keeping risk consistent even when you pyramid or add to positions. Without translating the percentage to a price, TradingView can only submit tick- or price-based stops.

  • Gather percentage inputs and convert them into decimal factors.
  • Compute long and short stop prices from strategy.position_avg_price.
  • Submit or refresh the exits each bar so they track pyramiding entries.
  • Plot stop levels to confirm they align with the percentage you expect.
CallPurpose
input.float(title, step)Collects user-configurable stop percentages.
strategy.position_avg_priceReturns the blended entry price to base calculations on.
strategy.exit(id, from_entry, stop)Submits the stop order at the calculated price.
strategy.position_sizeConfirms whether long or short exposure exists before sending stops.
plot(series, style=plot.style_cross)Visualises stop levels for debugging.
  1. Collect percentage inputs
    Accept separate values for long and short trades so you can fine-tune each side.

    longPercent = input.float(1.5, "Long stop (%)", minval=0.1, step=0.1) * 0.01
    shortPercent = input.float(1.2, "Short stop (%)", minval=0.1, step=0.1) * 0.01
  2. Translate percentages to stop prices
    Multiply the average entry by 1 - percent for longs and 1 + percent for shorts each bar.

    longStopPrice = strategy.position_avg_price * (1 - longPercent)
    shortStopPrice = strategy.position_avg_price * (1 + shortPercent)
  3. Submit exits whenever a position exists
    Guard by position side so you only submit the relevant stop.

    if strategy.position_size > 0
    strategy.exit("Long %SL", from_entry="Long", stop=longStopPrice)
    if strategy.position_size < 0
    strategy.exit("Short %SL", from_entry="Short", stop=shortStopPrice)
//@version=5
strategy("Percentage stop breakout", overlay=true, initial_capital=50_000, default_qty_type=strategy.percent_of_equity, default_qty_value=5)
fastLen = input.int(20, "Fast EMA", minval=1)
slowLen = input.int(60, "Slow EMA", minval=1)
longPct = input.float(1.8, "Long stop (%)", minval=0.1, step=0.1) * 0.01
shortPct = input.float(1.5, "Short stop (%)", minval=0.1, step=0.1) * 0.01
emaFast = ta.ema(close, fastLen)
emaSlow = ta.ema(close, slowLen)
goLong = ta.crossover(emaFast, emaSlow)
goShort = ta.crossunder(emaFast, emaSlow)
if goLong
strategy.entry("Long", strategy.long)
if goShort
strategy.entry("Short", strategy.short)
if strategy.position_size > 0
longStop = strategy.position_avg_price * (1 - longPct)
strategy.exit("Long %SL", from_entry="Long", stop=longStop)
plot(longStop, "Long stop", color=color.new(color.red, 0), style=plot.style_cross)
if strategy.position_size < 0
shortStop = strategy.position_avg_price * (1 + shortPct)
strategy.exit("Short %SL", from_entry="Short", stop=shortStop)
plot(shortStop, "Short stop", color=color.new(color.green, 0), style=plot.style_cross)
Why this works.
  • Stops refresh every bar, so pyramiding recalculates risk from the blended average price.
  • Separate inputs let you run asymmetric long/short risk without editing code.
  • Plotting the stop provides an immediate sanity check that the percentage lines up with the chart.
  • Convert percentages to decimals once. Passing 2 instead of 0.02 places the stop 200% away and effectively disables protection.
  • Clamp the result to sensible levels if you allow dynamic percentages (math.max(percent, 0.001)).
  • Use strategy.risk.max_position_loss() in addition to manual stops if you want broker-style fail-safes.
  • Remember that percentage stops move with the entry price—set them after every partial fill when pyramiding.

My stop plots above price on a long position

Section titled “My stop plots above price on a long position”

You’re probably adding instead of subtracting the percentage. Long stops should use (1 - percent); shorts use (1 + percent).

That’s expected. strategy.exit() cancels the exit order when the associated entry closes. As soon as a new position opens the code resubmits the stop.

Can I trail based on percentage gain instead?

Section titled “Can I trail based on percentage gain instead?”

Yes—replace the base calculation with strategy.position_avg_price * (1 + percent) for long take-profits or combine both stop and target in a single strategy.exit() call.

  • Percentage stops keep risk proportional to price level without hand-tuning ticks.
  • Multiply the average entry by 1 ± percent to convert the percentage into a stop price.
  • Reissue the stops every bar so they adjust after scaling in or out.
  • Visualise the stop level (or log it) to verify that the math matches your expectation.

Adapted from tradingcode.net, optimised for Algo Trade Analytics users.