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