Edgoro
Strategy

Pine Script strategy template (v6): risk-based sizing, ATR stop, take profit and filters

A reusable Pine Script v6 strategy template: risk-per-trade sizing, ATR stop, R-multiple target, date, session and daily filters. Swap in your own signal.

Most Pine Script strategies share the same skeleton: settings, a signal, filters, position sizing, orders and plots. Only the signal really changes from one idea to the next. Yet every new script tends to get the skeleton rewritten from scratch, and that's where the subtle bugs creep in: stops that drift, sizes that ignore the stop distance, session filters in the wrong time zone.

This article gives you a Pine Script v6 strategy template where the signal is one clearly marked block and everything else is ready: risk-per-trade sizing, an ATR stop, a take profit in multiples of the risk, date and session filters and a daily entry limit. The example signal, a moving-average cross, is only a placeholder. It's a starting point for testing your own rules, not a strategy to trade.

What the template does

  • Direction switch: long only, short only or both.
  • Risk-based sizing: each trade risks a fixed percentage of equity, whatever the stop distance.
  • ATR stop and R-multiple target, fixed when the signal fires so they don't drift.
  • Exposure cap: the position's value is limited to a multiple of equity.
  • Filters: an optional date range, an optional session window in a time zone you choose, and a maximum number of entries per day.
  • Costs and margin written into the declaration.
  • Plots for the stop and target while a trade is open.

The template

//@version=6
strategy("Strategy template (v6)", overlay = true,
  initial_capital = 10000,
  default_qty_type = strategy.fixed, default_qty_value = 1,
  commission_type = strategy.commission.percent, commission_value = 0.05,
  slippage = 2, margin_long = 100, margin_short = 100)

// ─── Inputs ─────────────────────────────────────────────
const string G_SIG  = "Signal"
const string G_RISK = "Risk"
const string G_FILT = "Filters"

direction  = input.string("Both", "Direction", options = ["Long only", "Short only", "Both"], group = G_SIG)
fastLen    = input.int(20, "Fast EMA", minval = 1, group = G_SIG)
slowLen    = input.int(50, "Slow EMA", minval = 2, group = G_SIG)

riskPct    = input.float(1.0, "Risk per trade (% of equity)", minval = 0.1, maxval = 5.0, step = 0.1, group = G_RISK)
atrLen     = input.int(14, "ATR length", minval = 1, group = G_RISK)
stopMult   = input.float(2.0, "Stop distance (x ATR)", minval = 0.5, step = 0.25, group = G_RISK)
targetR    = input.float(2.0, "Take profit (x stop distance)", minval = 0.5, step = 0.25, group = G_RISK)
maxLev     = input.float(1.0, "Max position value (x equity)", minval = 0.1, step = 0.1, group = G_RISK)
wholeUnits = input.bool(false, "Round size down to whole units", group = G_RISK)

useWindow  = input.bool(false, "Limit to a date range", group = G_FILT)
startTime  = input.time(timestamp("2020-01-01T00:00:00"), "From", group = G_FILT)
endTime    = input.time(timestamp("2030-01-01T00:00:00"), "To", group = G_FILT)
useSession = input.bool(false, "Only open trades in a session", group = G_FILT)
sessionStr = input.session("0800-1600", "Session", group = G_FILT)
sessionTz  = input.string("Europe/London", "Session time zone", group = G_FILT)
maxPerDay  = input.int(2, "Max entries per day (0 = no limit)", minval = 0, group = G_FILT)

// ─── Signal: replace this block with your own rules ─────
fast     = ta.ema(close, fastLen)
slow     = ta.ema(close, slowLen)
longRaw  = ta.crossover(fast, slow)
shortRaw = ta.crossunder(fast, slow)

// ─── Filters ────────────────────────────────────────────
inWindow  = not useWindow or (time >= startTime and time <= endTime)
inSession = not useSession or not na(time(timeframe.period, sessionStr, sessionTz))

var int entriesToday = 0
if timeframe.change("D")
    entriesToday := 0
dayOk = maxPerDay == 0 or entriesToday < maxPerDay

canTrade = inWindow and inSession and dayOk and strategy.position_size == 0
longOk   = direction != "Short only"
shortOk  = direction != "Long only"

// ─── Sizing ─────────────────────────────────────────────
atr = ta.atr(atrLen)

// Units to trade so that a stop `stopDist` away loses riskPct of equity
sizeFor(float stopDist) =>
    riskCash = strategy.convert_to_symbol(strategy.equity * riskPct / 100)
    qtyRisk  = riskCash / (stopDist * syminfo.pointvalue)
    qtyCap   = strategy.convert_to_symbol(strategy.equity * maxLev) / (close * syminfo.pointvalue)
    qty      = math.min(qtyRisk, qtyCap)
    wholeUnits ? math.floor(qty) : qty

// ─── Orders ─────────────────────────────────────────────
// One position at a time, so long and short trades can share the stored levels
var float stopPrice   = na
var float targetPrice = na

if canTrade and longOk and longRaw
    stopDist = stopMult * atr
    qty      = sizeFor(stopDist)
    if qty > 0
        stopPrice   := close - stopDist
        targetPrice := close + targetR * stopDist
        strategy.entry("Long", strategy.long, qty = qty)
        entriesToday += 1
else if canTrade and shortOk and shortRaw
    stopDist = stopMult * atr
    qty      = sizeFor(stopDist)
    if qty > 0
        stopPrice   := close + stopDist
        targetPrice := close - targetR * stopDist
        strategy.entry("Short", strategy.short, qty = qty)
        entriesToday += 1

strategy.exit("Long exit", "Long", stop = stopPrice, limit = targetPrice)
strategy.exit("Short exit", "Short", stop = stopPrice, limit = targetPrice)

// ─── Plots ──────────────────────────────────────────────
plot(fast, "Fast EMA", color.new(color.teal, 0))
plot(slow, "Slow EMA", color.new(color.orange, 0))
plot(strategy.position_size != 0 ? stopPrice : na, "Stop", color.new(color.red, 0), style = plot.style_linebr)
plot(strategy.position_size != 0 ? targetPrice : na, "Target", color.new(color.green, 0), style = plot.style_linebr)
bgcolor(useSession and not inSession ? color.new(color.gray, 92) : na, title = "Outside session")

Paste it into a new Strategy in the Pine Editor; how to add a Pine Script indicator to TradingView covers the editor if you're new to it.

How each block works

Inputs in groups

The group argument sorts the settings into sections in the strategy's settings dialog, so the risk inputs don't get lost among the signal inputs. The group names are const string values declared once, which avoids a typo creating a second group. direction uses options, so it appears as a dropdown.

The signal block

The rest of the template only needs two booleans from this block: longRaw and shortRaw, true on the bar where a long or short setup appears. Everything in it is calculated at the top level of the script, on every bar. That matters in Pine: functions such as ta.ema() and ta.crossover() keep their own history, and calling them inside an if gives inconsistent values.

Filters

  • Date range. Indicators keep calculating before startTime, so they're warmed up when trading starts. Useful for splitting history into a period to choose settings on and a period to check them on.
  • Session. time(timeframe.period, sessionStr, sessionTz) returns na when the bar is outside the session. The time zone input means "08:00–16:00 London time" stays correct whatever exchange the symbol trades on. Leave it at your market's own time zone if that's what you mean.
  • Daily limit. timeframe.change("D") is true on the first bar of each new trading day, which resets the counter. The day follows the symbol's daily session, which is not always midnight in your time zone.

Sizing from risk

The arithmetic is the one from position sizing and the 1% rule: units = cash at risk ÷ loss per unit at the stop.

  • strategy.equity * riskPct / 100 is the cash at risk in the account currency.
  • strategy.convert_to_symbol() converts it into the symbol's currency, so a USD account trading a EUR-quoted symbol still risks the right amount.
  • stopDist * syminfo.pointvalue is the loss per unit if the stop is hit. For stocks and crypto the point value is 1; for futures it's the contract multiplier.
  • qtyCap limits the position's value to maxLev times equity. Without it, a very tight stop would produce a huge position, and with 100% margin the simulated broker would answer with margin calls.
  • wholeUnits rounds down for markets that only trade whole shares or contracts. If rounding gives zero, the template skips the trade instead of quietly risking more than you asked.

The percentage is an approximation of the real loss. Commission, slippage and gaps through the stop all make a losing trade cost a bit more. The position size calculator is handy for checking a few trades by hand.

Orders and exits

Entries are market orders: the signal is evaluated when a bar closes and the order fills at the next bar's open. The stop and target are stored in var variables at that moment, measured from the signal bar's close, so they don't move as ATR changes. The fill price will differ slightly from that close; if you'd rather measure from the actual fill, use strategy.position_avg_price once the position is open.

Both strategy.exit() calls run on every bar. While a position is open, the matching one keeps its stop and limit orders in place; the other does nothing. More exit patterns, including break-even and trailing stops, are in strategy.exit() stop loss and take profit.

Plugging in your own signal

Replace the signal block, keep the two names, and the rest of the template works unchanged. For example, an RSI pullback in the direction of a 200 EMA:

rsi      = ta.rsi(close, 14)
trendUp  = close > ta.ema(close, 200)
longRaw  = ta.crossover(rsi, 30) and trendUp
shortRaw = ta.crossunder(rsi, 70) and not trendUp

If you swap the signal, also remove or change the two EMA plots at the bottom, which refer to fast and slow. Three rules keep any signal honest:

  • Calculate every ta.* call on its own line at the top level.
  • Use closed-bar values. The template only acts on bar close, so it doesn't repaint unless the signal itself does.
  • For higher-timeframe conditions, use the non-repainting pattern from multi-timeframe Pine Script with request.security().

Variations worth testing

  • Exit on the opposite signal: add if shortRaw → strategy.close("Long"), and the mirror for shorts.
  • Different risk for longs and shorts: add a second risk input and pass it to sizeFor().
  • Close at the end of the session: call strategy.close_all() on the first bar outside the session.
  • No target: pass only stop to strategy.exit() and exit on the signal instead.

Change one thing at a time and write down what each change did.

Checks before you trust the results

  • The risk check. In the list of trades, losing trades that hit the stop should each lose roughly the risk percentage of equity at the time. If they lose far more, look at gaps, costs or the point value.
  • Number of trades. The filters can cut trade counts sharply; a few dozen trades say very little.
  • Margin calls. Any "Margin call" rows mean the cap or the margin setting needs a look.
  • Session time zone. Check a few entries against the session you intended, especially around daylight-saving changes.
  • Costs. Set commission and slippage to what your broker really charges. The EMA example's reasoning about costs is in our EMA crossover strategy guide.

Sources and further reading

Try it with Edgoro

If you'd rather not edit the template by hand, describe your rules — "RSI(14) crosses above 30 while the close is above the 200 EMA, risk 1% per trade, stop at 1.5 × ATR, target 2R, London session only, max two trades a day" — and Edgoro's Pine Script generator writes the full v6 strategy in this shape, or the same rules as an MT5 Expert Advisor with the bots option. Test it in the Strategy Tester and on a demo account before relying on it.

This template is an example for testing and education, not a recommendation. Backtested results are hypothetical, and trading involves substantial risk of loss.

Build it with Edgoro

Describe your strategy in plain English and get a TradingView indicator in Pine Script v6 or a MetaTrader 5 Expert Advisor in minutes. Every first purchase is covered by a 7-day money-back guarantee.

Build your indicator with Edgoro