Pine Script conversion and backtesting

Convert a TradingView Indicator to a Strategy for Backtesting

To backtest a TradingView indicator with the built-in Strategy Tester, convert its accessible Pine Script source into a strategy: change the declaration, preserve the signal calculations, and specify entries, exits, position size, and trading costs. A chart arrow is a signal, not a filled order. The paired v6 examples below keep the same confirmed signals and show exactly which trading rules the conversion adds.

Convert with the AI Coding Agent

What changes when you convert an indicator?

Indicators can calculate series, keep custom state, and display statistics, but they do not submit orders to TradingView's strategy broker emulator. A strategy adds that simulation. Renaming the declaration alone does not define a trading system, and a custom indicator performance table is not automatically equivalent to Strategy Tester results.

DecisionIndicator exampleConverted strategy
SignalConfirmed EMA crossover with RSI filterSame calculation and every original signal arrow
EntryNo simulated ordersMarket entry only while flat; no reversal
ExitNo trading ruleFixed ATR-based tick distance from each entry fill
SizeNo order quantity10% of equity allocated per entry, not 10% equity at risk
CostsNo broker-emulator costsIllustrative 0.1% commission on each filled order and 2 ticks of slippage
AlertsNamed alertcondition() choicesStrategy order-fill alerts; alert() may be added for signals

Before: an indicator with confirmed buy and sell signals

This educational indicator uses a 20/50 EMA trend filter and an RSI filter. Crosses are evaluated on every bar. The barstate.isconfirmed gate makes the arrows wait until the realtime bar closes, giving the strategy a defined signal to preserve. No profitability claim is made for these rules.

Complete Pine Script v6 indicator. Use matching inputs in both examples.pinescript
//@version=6
indicator("Trend Pullback Indicator [Visual Only]", overlay = true)

// Indicator parameter inputs
fastLen = input.int(20, "Fast EMA Length", minval = 1)
slowLen = input.int(50, "Slow EMA Length", minval = 1)
rsiLen  = input.int(14, "RSI Length", minval = 1)
rsiOb   = input.int(70, "RSI Overbought", minval = 50, maxval = 100)
rsiOs   = input.int(30, "RSI Oversold", minval = 0, maxval = 50)

// Indicator calculations
fastEma = ta.ema(close, fastLen)
slowEma = ta.ema(close, slowLen)
rsiVal  = ta.rsi(close, rsiLen)

// Visual chart lines
plot(fastEma, "Fast EMA", color = color.blue, linewidth = 2)
plot(slowEma, "Slow EMA", color = color.orange, linewidth = 2)

// Evaluate crosses on every bar before combining conditions
crossUp = ta.crossover(close, fastEma)
crossDown = ta.crossunder(close, fastEma)

// Confirm signals at bar close in both versions
bullTrend   = fastEma > slowEma
bearTrend   = fastEma < slowEma
longSignal  = barstate.isconfirmed and bullTrend and crossUp and rsiVal > 50 and rsiVal < rsiOb
shortSignal = barstate.isconfirmed and bearTrend and crossDown and rsiVal < 50 and rsiVal > rsiOs

// Visual entry arrows (No broker execution or PnL tracking)
plotshape(longSignal, title = "Buy Arrow", style = shape.triangleup,
          location = location.belowbar, color = color.green, size = size.small)
plotshape(shortSignal, title = "Sell Arrow", style = shape.triangledown,
          location = location.abovebar, color = color.red, size = size.small)

// These conditions are selectable alerts in an indicator
alertcondition(longSignal, title = "Bullish Pullback Alert", message = "Bullish momentum detected")
alertcondition(shortSignal, title = "Bearish Pullback Alert", message = "Bearish momentum detected")

After: a strategy with explicit entry and exit rules

The converted example keeps the signal calculations and plots. It ignores new signals while a position is open. On an accepted signal it snapshots ATR, converts the stop and target distances to ticks, and submits the bracket together with the entry. The bracket distances stay fixed for that trade. With these calculation settings, a market entry normally fills at the following bar open, with the configured slippage.

Complete Pine Script v6 conversion example. Costs and sizing are illustrative.pinescript
//@version=6
strategy("Trend Pullback Strategy [Conversion Demo]", 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,
     slippage = 2, pyramiding = 0,
     margin_long = 100, margin_short = 100,
     calc_on_every_tick = false, calc_on_order_fills = false,
     process_orders_on_close = false)

// Indicator parameter inputs
fastLen = input.int(20, "Fast EMA Length", minval = 1)
slowLen = input.int(50, "Slow EMA Length", minval = 1)
rsiLen  = input.int(14, "RSI Length", minval = 1)
rsiOb   = input.int(70, "RSI Overbought", minval = 50, maxval = 100)
rsiOs   = input.int(30, "RSI Oversold", minval = 0, maxval = 50)

// Illustrative exit inputs; choose values for your own rules
atrLen = input.int(14, "ATR Length", minval = 1)
slMult = input.float(2.0, "Stop ATR Multiple", minval = 0.1)
tpMult = input.float(3.0, "Target ATR Multiple", minval = 0.1)

// Indicator calculations
fastEma = ta.ema(close, fastLen)
slowEma = ta.ema(close, slowLen)
rsiVal  = ta.rsi(close, rsiLen)
atrVal = ta.atr(atrLen)

// Visual chart lines
plot(fastEma, "Fast EMA", color = color.blue, linewidth = 2)
plot(slowEma, "Slow EMA", color = color.orange, linewidth = 2)

// Evaluate crosses on every bar before combining conditions
crossUp = ta.crossover(close, fastEma)
crossDown = ta.crossunder(close, fastEma)

// Confirm signals at bar close in both versions
bullTrend   = fastEma > slowEma
bearTrend   = fastEma < slowEma
longSignal  = barstate.isconfirmed and bullTrend and crossUp and rsiVal > 50 and rsiVal < rsiOb
shortSignal = barstate.isconfirmed and bearTrend and crossDown and rsiVal < 50 and rsiVal > rsiOs

// Keep every signal arrow, including signals skipped while in a position
plotshape(longSignal, title = "Buy Arrow", style = shape.triangleup,
          location = location.belowbar, color = color.green, size = size.small)
plotshape(shortSignal, title = "Sell Arrow", style = shape.triangledown,
          location = location.abovebar, color = color.red, size = size.small)

// Snapshot ATR distances when submitting the entry and its bracket.
// loss/profit are ticks from the eventual fill, not absolute prices.
if strategy.position_size == 0 and not na(atrVal)
    stopTicks = math.max(1, math.round(atrVal * slMult / syminfo.mintick))
    targetTicks = math.max(1, math.round(atrVal * tpMult / syminfo.mintick))
    if longSignal
        strategy.entry("Long", strategy.long)
        strategy.exit("Exit Long", from_entry = "Long", loss = stopTicks, profit = targetTicks)
    else if shortSignal
        strategy.entry("Short", strategy.short)
        strategy.exit("Exit Short", from_entry = "Short", loss = stopTicks, profit = targetTicks)

Position size, exits, and costs are separate choices

Ten percent of equity is an allocation to the position. It is not a promise to lose at most ten percent, or a fixed percentage risk rule. The loss depends on quantity, exit distance, instrument value, fills, and fees. A stop can fill beyond its trigger after a gap.

  • The example uses strategy.exit() loss and profit as tick distances. Use stop and limit when your rule specifies absolute price levels.
  • For an exit on an opposite signal, define a strategy.close() rule. For reversal, strategy.entry() can reverse an existing position; the flat-position gate in this example deliberately prevents that.
  • The 0.1% commission and 2-tick slippage are placeholders. Replace them with assumptions for your broker, instrument, order size, and market conditions, then test a more expensive case.
  • Leave extra recalculation settings off for the initial comparison. calc_on_order_fills recalculates after fills; process_orders_on_close changes eligible fill timing. Neither recreates missing historical tick data.

How to verify the conversion in TradingView

Use this as a repeatable review procedure. The examples have syntax checks, but this page does not report an executed backtest, win rate, or return. A successful compile does not establish matching behavior or trading value.

  • Open a standard candlestick chart. Record the symbol and exchange, timeframe, session, timezone, inputs, loaded history, and test interval.
  • Paste the indicator into Pine Editor, save it, and add it to the chart. Save the strategy as a separate script and add it with the same signal inputs.
  • Compare the two scripts' arrows on closed bars. They should match; the strategy keeps arrows even when its position gate skips an entry.
  • Inspect the strategy trade list. For each accepted signal, check the order ID, next available fill, direction, quantity, and fees. An arrow on a bar close and a fill on the next bar are different events.
  • For each bracket, use the signal-bar ATR and minimum tick to calculate the rounded stop/target distances. Compare them with the entry fill and recorded exit. Account for gaps and slippage.
  • Check a period with repeated signals, an opposite signal during a position, and a bar touching both exit levels. Broker-emulator assumptions matter when historical OHLC cannot resolve the order of intrabar touches.
  • Keep a separate period for evaluation after choosing rules. Reload the chart and compare confirmed signals, especially if your own indicator uses pivots, offsets, or other timeframes.

Alerts and version changes that affect conversion

A valid alertcondition() call in a strategy does not itself cause a compilation error. It simply cannot provide an alertcondition-based alert from that strategy. Use order-fill alerts for simulated fills, or alert() for a signal event. Create the running alert in TradingView's interface and recreate it after changing the script or inputs.

  • The when parameter was deprecated in Pine Script v5 and removed in v6. Conditional if blocks work for the v6 order calls shown here.
  • An indicator can update during an open realtime bar while a default strategy calculates at bar close. Decide whether to preserve intrabar signals or require confirmation before comparing results.
  • Converting a repainting signal into an order does not repair its history. Inspect the source for future-data leakage or signals drawn back onto earlier bars.

Can you backtest a protected or invite-only indicator?

You cannot edit hidden source directly. Ask the author for a strategy version or documented trading rules. If the indicator exposes a suitable numeric plot in TradingView's source selector, a separate strategy can use input.source() to consume that series and apply your rules. This depends on the available output and its meaning; chart drawings and alert messages are not automatically source inputs. Without a usable series or reproducible rules, forward observation is possible, but it does not reconstruct a historical Strategy Tester backtest.

What to give an AI coding agent

Provide accessible source and a trading specification: allowed directions, confirmation timing, repeated-entry policy, exits, sizing, and cost assumptions. Ask the agent to explain every behavior it adds. Review the resulting code and its trade list before using the output.

  • Conversion brief for this example: preserve the EMA/RSI calculations and all confirmed signal plots; enter only while flat; ignore opposite signals during a position; use fixed ATR tick brackets submitted with each entry.
  • Provide your own cost and sizing requirements. Do not ask the agent to infer an effective strategy from visual arrows alone.
Where Pineify fits

Pine Script AI Coding Agent

Provide your accessible indicator source and explicit trading rules to request a strategy conversion. Review the code and validate the simulated trades in TradingView.

Also useful: Backtest Deep Report. After checking the conversion in TradingView, review the report workflow for further analysis of your strategy backtest.

Convert with the AI Coding Agent

Frequently asked questions

Educational code, not investment advice. Simulated results depend on data and execution assumptions and do not guarantee future performance.

Sources and verification