Free Pine Script generator, tested before you trade it

Most Pine Script generators write code. QuantProof tests your idea first: describe the strategy in plain English, see every trade and whether it beat buy & hold, then copy the TradingView script.

Try it with an example →Write your own

How it works

  1. Describe it. "Buy Apple when the 14 day RSI is below 35 and the close is above the 200 day SMA. Sell when the 14 day RSI is above 60. Stop loss 8%."
  2. See the backtest. Every trade on the chart, win rate, worst drop, and the buy & hold comparison.
  3. Copy the Pine Script. An indicator with buy/sell signals, stop loss and take profit lines, and alerts, or a strategy script for TradingView's tester.

Unclear description? It asks you a question instead of guessing. Found the strategy on YouTube? Paste the video link.

Example output

Generated from the rules above (RSI dip in an uptrend):

//@version=5
// Indicator generated by QuantProof from your rules.
// Built for AAPL on the daily (1D) chart. Config ec92605829.
//   Buy when:  rsi(14) < 35 AND close > sma(200)
//   Sell when: rsi(14) > 60
//   Stop loss: 8%
//
// Execution, identical in the QuantProof backtest, this script and the alerts:
//   rules are read on the bar close, the order fills at the next bar's open
//   5 bps slippage per side and 1 bps fee per side are applied to every fill
//   stop loss and take profit are measured from the filled entry price, stop first when a bar hits both
//   one setup at a time: no new entry until the open one closes
// Marks where your rules say buy and sell, and adds alerts you can turn on in TradingView.

indicator("RSI dip in an uptrend signals", overlay=true, max_boxes_count=300, max_lines_count=300, max_labels_count=300)

// Prices are written the way the market quotes them: a $500 coin does not have a target of 566.130924.
qpPrice(x) => str.tostring(x, x >= 1000 ? "#.##" : x >= 100 ? "#.###" : x >= 1 ? "#.#####" : "#.########")

rsi14 = ta.rsi(close, 14)
sma200 = ta.sma(close, 200)
entrySignal = (rsi14 < 35) and (close > sma200)
exitSignal = (rsi14 > 60)

// One setup at a time, in the states QuantProof uses:
//   flat -> armed (signal on the close) -> active (filled at the next open) -> closing (exit signal) -> flat.
// While a setup is armed, active or closing, new entry signals are ignored.
var string state = "flat"
var float entryPrice = na
var string exitReason = ""
slipUp = 1 + 0.0005
slipDown = 1 - 0.0005

// 1. Orders queued on the previous close fill at this bar's open.
filledNow = state == "armed"
exitedAtOpen = state == "closing"
if filledNow
    entryPrice := open * slipUp
    state := "active"
if exitedAtOpen
    state := "flat"

// 2. While the setup is active, the stop is checked before the target, as in the backtest.
stopLevel = entryPrice * (1 - 8.0 / 100)
targetLevel = na
stopHit = state == "active" and low <= stopLevel
targetHit = false
hitNow = stopHit or targetHit
if hitNow
    exitReason := stopHit ? "stop loss" : "take profit"
    state := "flat"

// 3. Rules are read on this close; the order waits for the next open.
// A setup can arm on the same bar an earlier one closed, exactly as the backtest does.
armNow = state == "flat" and entrySignal
closeNow = state == "active" and exitSignal
if armNow
    state := "armed"
if closeNow
    exitReason := "exit rule"
    state := "closing"

exitPrice = stopHit ? math.min(open, stopLevel) * slipDown : targetHit ? math.max(open, targetLevel) * slipDown : open * slipDown
closedNow = hitNow or exitedAtOpen
if closedNow
    label.new(bar_index, low, exitReason + " @ " + qpPrice(exitPrice), style=label.style_label_up, color=color.new(color.red, 25), textcolor=color.white, size=size.small)

// Levels stay drawn through the bar where the trade closes.
wasInTrade = state == "active" or state == "closing" or closedNow
levelBase = entryPrice

// The setup drawn as a position: target zone above the entry, stop zone below it, and the entry
// itself as a dashed line between them.
var box tpBox = na
var box slBox = na
var line entryLine = na
if filledNow
    if not na(targetLevel)
        tpBox := box.new(bar_index, math.max(targetLevel, entryPrice), bar_index + 1, math.min(targetLevel, entryPrice), border_color=color.new(color.teal, 55), bgcolor=color.new(color.teal, 88))
    if not na(stopLevel)
        slBox := box.new(bar_index, math.max(entryPrice, stopLevel), bar_index + 1, math.min(entryPrice, stopLevel), border_color=color.new(color.gray, 55), bgcolor=color.new(color.gray, 82))
    entryLine := line.new(bar_index, entryPrice, bar_index + 1, entryPrice, color=color.new(color.silver, 10), style=line.style_dashed, width=1)
if wasInTrade
    if not na(tpBox)
        box.set_right(tpBox, bar_index + 1)
    if not na(slBox)
        box.set_right(slBox, bar_index + 1)
    if not na(entryLine)
        line.set_x2(entryLine, bar_index + 1)
// Position levels from your inputs
float entryLine = wasInTrade ? levelBase : na
float tpLine = na
float slLine = entryLine * (1 - 8.0 / 100)
pEntry = plot(entryLine, "Entry", color.new(color.gray, 0), 1, plot.style_linebr)
pTp = plot(tpLine, "Take profit (not set)", color.new(color.green, 0), 2, plot.style_linebr)
pSl = plot(slLine, "Stop loss 8%", color.new(color.red, 0), 2, plot.style_linebr)
fill(pEntry, pTp, color.new(color.green, 85))
fill(pEntry, pSl, color.new(color.red, 85))
if (wasInTrade) and not (wasInTrade)[1]
    label.new(bar_index, high, "Entry " + qpPrice(entryLine) + "\nSL " + qpPrice(slLine) + " (-8%)", style=label.style_label_lower_left, color=color.new(color.gray, 20), textcolor=color.white, size=size.small)

plot(rsi14, "RSI(14)", display=display.data_window)
plot(sma200, "SMA(200)", color.aqua)
plotshape(armNow, "Setup armed (fills next open)", shape.triangleup, location.belowbar, color.new(color.teal, 40), size=size.tiny)
plotshape(filledNow, "Entry filled", shape.labelup, location.belowbar, color.new(color.teal, 0), text="BUY", textcolor=color.white, size=size.small)
plotshape(closedNow, "Exit", shape.labeldown, location.abovebar, color.new(color.red, 0), text="SELL", textcolor=color.white, size=size.small)
alertcondition(armNow, "Setup armed", "RSI dip in an uptrend: {{ticker}} setup armed on the close, fills at the next open")
alertcondition(filledNow, "Entry filled", "RSI dip in an uptrend: {{ticker}} entry filled at the open")
alertcondition(closedNow, "Setup closed", "RSI dip in an uptrend: {{ticker}} setup closed")

Strategies with ready-made indicators