25. Example Custom Strategy
//@version=5
strategy(
"Perpbot SMA Example (Fixed TP/SL + BE)",
overlay=true,
initial_capital=1000,
pyramiding=1,
default_qty_type = strategy.cash,
default_qty_value = 1000,
slippage = 0,
calc_on_every_tick = true,
calc_on_order_fills = true,
process_orders_on_close = false)
// ───────── SMA Inputs & Signals ─────────
fastLen = input.int(10, "Fast SMA Length", minval = 1)
slowLen = input.int(50, "Slow SMA Length", minval = 1)
fastSMA = ta.sma(close, fastLen)
slowSMA = ta.sma(close, slowLen)
// Entry / exit signals based on SMA crossovers
buy_signal = ta.crossover(fastSMA, slowSMA) // long entry
sell_signal = ta.crossunder(fastSMA, slowSMA) // long exit filter
short_entry_signal = ta.crossunder(fastSMA, slowSMA) // short entry
short_exit_signal = ta.crossover(fastSMA, slowSMA) // short exit filter
plot(fastSMA, "Fast SMA", color=color.new(color.teal, 0))
plot(slowSMA, "Slow SMA", color=color.new(color.orange, 0))
plotshape(buy_signal, title="Buy Signal", location=location.belowbar,
color=color.green, style=shape.triangleup, size=size.small)
plotshape(sell_signal, title="Sell Signal", location=location.abovebar,
color=color.red, style=shape.triangledown, size=size.small)
plotshape(short_entry_signal, title="Short Entry", location=location.abovebar,
color=color.new(color.red, 0), style=shape.circle, size=size.tiny)
plotshape(short_exit_signal, title="Short Exit", location=location.belowbar,
color=color.new(color.green, 0), style=shape.circle, size=size.tiny)
// ───────── Risk / Timing ─────────
stop_loss_pct = input.float(1.0, "Stop Loss %", step=0.1) // fixed from entry
tp_percent = input.float(7.0, "TP %", step=0.1) // fixed from entry
be_trigger_pct = input.float(3.2, "BE Trigger (+%)", step=0.1) // when unrealized hits +X%
be_offset_pct = input.float(0.4, "BE Stop (+%)", step=0.05) // lock profit after BE trigger
max_holding_time = 40000 * 60 * 60 * 1000
buy_cooldown_time = 30 * 60 * 1000
sell_cooldown_time= 30 * 60 * 1000
// ───────── State ─────────
var float entry_price = na
var int entry_time = na
var int last_buy_close_time = na
var bool long_be_active = false
var float long_tp_price = na
var float long_sl_price = na
var float short_entry_price = na
var int short_entry_time = na
var int last_short_close_time = na
var bool short_be_active = false
var float short_tp_price = na
var float short_sl_price = na
// ───────── Helpers ─────────
f_can_buy() =>
na(last_buy_close_time) or (time - last_buy_close_time) >= buy_cooldown_time
f_can_sell_long() =>
na(entry_time) or (time - entry_time) >= sell_cooldown_time
f_can_short() =>
na(last_short_close_time) or (time - last_short_close_time) >= buy_cooldown_time
f_round_to_tick(p) =>
math.round(p / syminfo.mintick) * syminfo.mintick
// ───────── LONG ENTRY ─────────
if (buy_signal and strategy.opentrades == 0 and f_can_buy())
// Flip from short to long if needed
if (strategy.position_size < 0)
strategy.close("Short", comment="Flip to Long")
last_short_close_time := time
strategy.entry("Buy", strategy.long, alert_message = "long")
// record entry / reset state
entry_price := close
entry_time := time
long_be_active := false
// FIXED TP/SL from entry (do not refresh every bar)
long_tp_price := f_round_to_tick(entry_price * (1 + tp_percent / 100.0))
long_sl_price := f_round_to_tick(entry_price * (1 - stop_loss_pct / 100.0))
// ───────── LONG MANAGEMENT (TP/SL + BE + time/signal exit) ─────────
if (strategy.position_size > 0)
// 1) Breakeven trigger: once price reaches +be_trigger_pct from *entry*
if not long_be_active and high >= entry_price * (1 + be_trigger_pct / 100.0)
long_be_active := true
// move SL once to BE+offset% (and then keep it)
long_sl_price := f_round_to_tick(entry_price * (1 + be_offset_pct / 100.0))
// 2) Primary TP/SL (these prices are fixed except for BE move)
strategy.exit(
id = "L-TP/SL",
from_entry = "Buy",
stop = long_sl_price,
limit = long_tp_price,
alert_message = "close_long")
// 3) Gap & penetration hard guards (still allowed, but SL itself doesn't “refresh” logic)
if (open < long_sl_price)
strategy.close("Buy", comment="Gap SL")
last_buy_close_time := time
if (low <= long_sl_price)
strategy.close("Buy", comment="Hard SL")
last_buy_close_time := time
// 4) Time-based & SMA-based manual exits
holding_time = time - entry_time
if (holding_time >= max_holding_time)
strategy.close("Buy", comment="Time Limit")
last_buy_close_time := time
if (holding_time >= sell_cooldown_time and f_can_sell_long())
if (sell_signal)
strategy.close("Buy", comment="Sell Signal (SMA cross down)")
last_buy_close_time := time
// ───────── SHORT ENTRY ─────────
if (short_entry_signal and strategy.opentrades == 0 and f_can_short())
// Flip from long to short if needed
if (strategy.position_size > 0)
strategy.close("Buy", comment="Flip to Short")
last_buy_close_time := time
strategy.entry("Short", strategy.short, alert_message = "short")
short_entry_price := close
short_entry_time := time
short_be_active := false
// FIXED TP/SL from entry
short_tp_price := f_round_to_tick(short_entry_price * (1 - tp_percent / 100.0))
short_sl_price := f_round_to_tick(short_entry_price * (1 + stop_loss_pct / 100.0))
// ───────── SHORT MANAGEMENT (TP/SL + BE) ─────────
if (strategy.position_size < 0)
// 1) BE trigger for shorts: price moves down by be_trigger_pct from entry
if not short_be_active and low <= short_entry_price * (1 - be_trigger_pct / 100.0)
short_be_active := true
// move SL once to entry - be_offset_pct (lock profit)
short_sl_price := f_round_to_tick(short_entry_price * (1 - be_offset_pct / 100.0))
// 2) Primary TP/SL (fixed except for BE move)
strategy.exit(
id = "S-TP/SL",
from_entry = "Short",
stop = short_sl_price,
limit = short_tp_price,
alert_message = "close_short")
// 3) Gap & hard guards
if (open > short_sl_price)
strategy.close("Short", comment="Gap SL")
last_short_close_time := time
if (high >= short_sl_price)
strategy.close("Short", comment="Hard SL")
last_short_close_time := time
// ───────── Cleanup of BE flags when flat or opposite ─────────
if (strategy.position_size <= 0)
long_be_active := false
if (strategy.position_size >= 0)
short_be_active := false
Last updated