You have an indicator that marks buy and sell signals on the chart. You want to know whether those signals have historically been profitable. The answer is not in the indicator — it is in the Strategy Tester. And getting to the Strategy Tester means converting your indicator into a strategy. That conversion is smaller than most people expect.
This tutorial walks through the full conversion from a working Pine Script indicator to a backtestable strategy in Pine Script v6, including position sizing and stop placement. Every change is explained so you understand what you are trading off, not just how to make the script compile.
What changes between indicator and strategy
| indicator() | strategy() | |
|---|---|---|
| Declaration | indicator("Name", overlay=true) | strategy("Name", overlay=true, initial_capital=10000, ...) |
| Order functions | Not available | strategy.entry(), strategy.exit(), strategy.close() |
| Backtesting | Not available | Strategy Tester tab with full performance metrics |
| The "when=" parameter | n/a | Removed in v6 — use if blocks instead |
| plotshape / plot | Works everywhere | Still works; keep it for visual confirmation |
The structural difference is small: one function call changes, order functions become available, and the Strategy Tester tab appears. The conceptual difference is larger: you are now expressing not just when signals occur but exactly what happens when each one fires — how much to buy, where to place a stop, when to exit.
Step 1: the indicator to start with
Here is a simple RSI oversold indicator. It marks confirmed crossovers above the oversold level with a green triangle:
//@version=6
indicator("RSI Oversold Signal", overlay=true)
rsiLen = input.int(14, "RSI Length", minval=2)
oversold = input.int(30, "Oversold level", minval=1, maxval=49)
rsiValue = ta.rsi(close, rsiLen)
// Signal fires on confirmed bar only
buySignal = ta.crossover(rsiValue, oversold) and barstate.isconfirmed
plotshape(buySignal, "Buy signal", shape.triangleup, location.belowbar, color.green)This compiles and plots correctly. It uses barstate.isconfirmed to ensure signals only appear on closed bars, so the shapes will not repaint. For a full explanation of why that matters, see the barstate.isconfirmed guide.
Step 2: converting to a strategy
Three things change. The indicator() declaration becomesstrategy() with additional parameters for capital and commission. The plotshape calls are joined bystrategy.entry() and strategy.close() wrapped inif blocks. And the barstate.isconfirmed guard is no longer needed on the order calls because strategies by default fill at the open of the next bar, which is always a confirmed event.
//@version=6
// Step 1: change indicator() to strategy() and add the required parameters.
// initial_capital and commission_value give the Tester realistic assumptions.
strategy("RSI Oversold Strategy",
overlay = true,
initial_capital = 10000,
default_qty_type = strategy.percent_of_equity,
default_qty_value = 10,
commission_type = strategy.commission.percent,
commission_value = 0.1)
rsiLen = input.int(14, "RSI Length", minval=2)
oversold = input.int(30, "Oversold level", minval=1, maxval=49)
overbought = input.int(70, "Overbought level", minval=51, maxval=99)
rsiValue = ta.rsi(close, rsiLen)
// Step 2: hoist crossover calls before 'and' to avoid v6 lazy-evaluation bug.
crossUp = ta.crossover(rsiValue, oversold)
crossDown = ta.crossunder(rsiValue, overbought)
// Step 3: wrap order calls in if blocks (strategy.entry no longer accepts 'when=').
if crossUp
strategy.entry("Long", strategy.long)
if crossDown
strategy.close("Long")
// Step 4: keep the visual aid so you can see signals on the chart.
plotshape(crossUp, "Buy", shape.triangleup, location.belowbar, color.green)
plotshape(crossDown, "Exit", shape.triangledown, location.abovebar, color.red)A few v6-specific things to note. The when= parameter that older Pine Script strategy examples used inside strategy.entry() was removed in v6. The correct pattern is to wrap the call in anif block. The crossover calls are hoisted to separate variables before the if conditions — this is the v6 lazy evaluation pattern explained in the multi-timeframe indicator guide. And commission_value = 0.1 sets a 0.1% round-trip cost, which is a more realistic assumption than the default of zero.
Load this into TradingView and open the Strategy Tester tab. You will see the full trade list, equity curve, and performance metrics. For a guide to reading those numbers honestly, see Pine Script Strategy Tester: every metric explained.
Step 3: adding position sizing and a stop loss
A strategy without a stop loss is not a realistic test of anything. The version above closes on an RSI crossunder, which could mean the position is held for days during a drawdown. A more realistic version sizes each position based on a volatility stop and risks a fixed percentage of equity per trade:
//@version=6
strategy("RSI Strategy — ATR position sizing",
overlay = true,
initial_capital = 10000,
commission_type = strategy.commission.percent,
commission_value = 0.1)
rsiLen = input.int(14, "RSI Length")
atrLen = input.int(14, "ATR Length")
atrMult = input.float(1.5, "ATR stop multiplier", step=0.1)
riskPct = input.float(1.0, "Risk per trade %", step=0.1)
rsiValue = ta.rsi(close, rsiLen)
atrValue = ta.atr(atrLen)
crossUp = ta.crossover(rsiValue, 30)
if crossUp
stopDistance = atrValue * atrMult
// Avoid dividing by zero if ATR is somehow na on this bar.
if stopDistance > 0
riskAmount = strategy.equity * (riskPct / 100)
qty = riskAmount / stopDistance
strategy.entry("Long", strategy.long, qty=qty)
strategy.exit("Long SL", "Long", stop=close - stopDistance)
if ta.crossunder(rsiValue, 70)
strategy.close("Long")This version computes the stop distance as an ATR multiple, then sizes the position so that if the stop is hit, the loss equals exactly the risk percentage of current equity. This is percent-of-equity risk sizing, which is covered in depth in the position sizing in Pine Script guide.
What the Strategy Tester will tell you
With a realistic strategy now running, the Tester shows you net profit, win rate, profit factor, maximum drawdown, Sharpe ratio, and the full trade list. The most important number to look at first is not net profit — it is maximum drawdown. A strategy that returned 80% but had a 70% drawdown at some point in the middle is not tradeable for most people regardless of the final number.
The second thing to check is trade count. Fewer than 50 trades and no metric in the Tester is meaningful yet. The full metrics guide explains what each number actually measures and where each one misleads.
Doing this faster
The indicator-to-strategy conversion is mechanical once you know the pattern, but the position sizing, stop logic, and v6 syntax details are where most conversions introduce bugs. Describing your indicator's signal logic and your intended exit rules in plain English to PineScripter produces a complete strategy with correct v6 syntax, sizing, and stop placement in one pass. If any compile error appears, the built-in loop corrects it without manual intervention. The result is code you can paste directly into the Pine Editor and open the Strategy Tester immediately.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.