Strategy · Pine Script v5 · On the chart

RSI Mean Reversion

by GifariKemal· code credited to SURIOTA Trading Team

This strategy plots entry signals, stop loss, take profit levels, and an information panel directly on the price chart.

View on GitHub

GitHub stars3on the repository
Pine versionv5runs as is
Lines of code289
Settings21inputs
LicenceMIT
Last update3 days ago25 Oct 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new strategy, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart. The trades and results appear in the Strategy Tester tab; adjust the settings with the gear icon.

Source Code

289 lines of Pine Script v5 from pinescript/RSI_MeanReversion_v37.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.

RSI_MeanReversion_v37.pine
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © SURIOTA Trading Team

//@version=5
strategy("RSI Mean Reversion v3.7",
     shorttitle="RSI v3.7",
     overlay=true,
     initial_capital=10000,
     default_qty_type=strategy.percent_of_equity,
     default_qty_value=1,
     commission_type=strategy.commission.cash_per_contract,
     commission_value=7,          // $7 per standard lot per side (typical forex)
     slippage=10,                 // 10 ticks = 1 pip (for 5-digit broker)
     pyramiding=0,
     calc_on_every_tick=false,
     process_orders_on_close=true)

// ══════════════════════════════════════════════════════════════════════════════
// INPUTS
// ══════════════════════════════════════════════════════════════════════════════

// RSI Settings
grp_rsi = "RSI Settings"
rsiPeriod = input.int(10, "RSI Period", minval=2, maxval=50, group=grp_rsi)
rsiOversold = input.int(42, "RSI Oversold", minval=10, maxval=50, group=grp_rsi)
rsiOverbought = input.int(58, "RSI Overbought", minval=50, maxval=90, group=grp_rsi)

// ATR Settings
grp_atr = "ATR Settings"
atrPeriod = input.int(14, "ATR Period", minval=1, maxval=50, group=grp_atr)
atrLookback = input.int(100, "ATR Percentile Lookback", minval=20, maxval=200, group=grp_atr)
atrMinPct = input.float(20.0, "Min ATR Percentile", minval=0, maxval=50, group=grp_atr)
atrMaxPct = input.float(80.0, "Max ATR Percentile", minval=50, maxval=100, group=grp_atr)

// SL/TP Settings
grp_sltp = "SL/TP Settings"
slMult = input.float(1.5, "SL Multiplier (ATR)", minval=0.5, maxval=5, step=0.1, group=grp_sltp)
tpLow = input.float(2.4, "TP Low Volatility", minval=1, maxval=10, step=0.1, group=grp_sltp)
tpMed = input.float(3.0, "TP Medium Volatility", minval=1, maxval=10, step=0.1, group=grp_sltp)
tpHigh = input.float(3.6, "TP High Volatility", minval=1, maxval=10, step=0.1, group=grp_sltp)
tpTimeBonus = input.float(0.35, "TP Time Bonus (12-16 UTC)", minval=0, maxval=1, step=0.05, group=grp_sltp)

// Time Filter
grp_time = "Time Filter"
tradingStartHour = input.int(7, "Trading Start Hour (UTC)", minval=0, maxval=23, group=grp_time)
tradingEndHour = input.int(22, "Trading End Hour (UTC)", minval=0, maxval=23, group=grp_time)
skipHour = input.int(12, "Skip Hour (UTC)", minval=0, maxval=23, group=grp_time)
tpBonusStart = input.int(12, "TP Bonus Start Hour", minval=0, maxval=23, group=grp_time)
tpBonusEnd = input.int(16, "TP Bonus End Hour", minval=0, maxval=23, group=grp_time)

// Position Management
grp_risk = "Risk Management"
maxHoldingBars = input.int(46, "Max Holding (Bars)", minval=1, maxval=100, group=grp_risk)

// Visual Settings
grp_visual = "Visual Settings"
showSignals = input.bool(true, "Show Entry Signals", group=grp_visual)
showSLTP = input.bool(true, "Show SL/TP Lines", group=grp_visual)
showInfo = input.bool(true, "Show Info Panel", group=grp_visual)

// ══════════════════════════════════════════════════════════════════════════════
// CALCULATIONS
// ══════════════════════════════════════════════════════════════════════════════

// RSI Calculation
rsi = ta.rsi(close, rsiPeriod)

// ATR Calculation
atr = ta.atr(atrPeriod)

// ATR Percentile: % of historical values below current value
atrPercentile(src, len) =>
    if len <= 0
        50.0
    else
        count = 0
        for i = 1 to len  // Compare against last 'len' bars (historical only)
            if src[i] < src  // src is current bar value
                count += 1
        result = count / len * 100.0
        result

atrPct = atrPercentile(atr, atrLookback)

// Time Functions (UTC)
currentHour = hour(time, "UTC")
currentWeekday = dayofweek(time)

// ══════════════════════════════════════════════════════════════════════════════
// FILTERS
// ══════════════════════════════════════════════════════════════════════════════

// Weekend filter (1=Sunday, 7=Saturday)
isWeekend = currentWeekday == 1 or currentWeekday == 7

// Trading hours filter
isValidHour = currentHour >= tradingStartHour and currentHour < tradingEndHour and currentHour != skipHour

// ATR filter
isValidATR = atrPct >= atrMinPct and atrPct <= atrMaxPct

// Combined filter
canTrade = not isWeekend and isValidHour and isValidATR

// ══════════════════════════════════════════════════════════════════════════════
// TP MULTIPLIER
// ══════════════════════════════════════════════════════════════════════════════

getTPMultiplier() =>
    baseTP = atrPct < 40 ? tpLow : (atrPct > 60 ? tpHigh : tpMed)
    isTPBonusTime = currentHour >= tpBonusStart and currentHour < tpBonusEnd
    result = isTPBonusTime ? baseTP + tpTimeBonus : baseTP
    result

tpMult = getTPMultiplier()

// ══════════════════════════════════════════════════════════════════════════════
// SIGNALS
// ══════════════════════════════════════════════════════════════════════════════

// Entry signals
buySignal = canTrade and rsi < rsiOversold and strategy.position_size == 0
sellSignal = canTrade and rsi > rsiOverbought and strategy.position_size == 0

// Persistent SL/TP Levels (stored at entry, not recalculated each bar)
var float storedSL = na
var float storedTP = na

// Track position entry bar
var int entryBar = na

// ══════════════════════════════════════════════════════════════════════════════
// STRATEGY EXECUTION
// ══════════════════════════════════════════════════════════════════════════════

// Entry logic - store SL/TP at entry time
if buySignal
    storedSL := close - atr * slMult
    storedTP := close + atr * tpMult
    entryBar := bar_index
    strategy.entry("Long", strategy.long)

else if sellSignal
    storedSL := close + atr * slMult
    storedTP := close - atr * tpMult
    entryBar := bar_index
    strategy.entry("Short", strategy.short)

// Exit management (separate from entry signals)
if strategy.position_size > 0 and not na(storedSL) and not na(storedTP)
    strategy.exit("Long Exit", "Long", stop=storedSL, limit=storedTP)

if strategy.position_size < 0 and not na(storedSL) and not na(storedTP)
    strategy.exit("Short Exit", "Short", stop=storedSL, limit=storedTP)

// Max holding exit with safety check
barsInPosition = strategy.position_size != 0 and not na(entryBar) ? bar_index - entryBar : 0
maxHoldingExit = barsInPosition >= maxHoldingBars and strategy.position_size != 0

if maxHoldingExit
    strategy.close_all(comment="Timeout")
    entryBar := na
    storedSL := na
    storedTP := na

// Reset stored values when position closes
if strategy.position_size == 0 and strategy.position_size[1] != 0
    entryBar := na
    storedSL := na
    storedTP := na

// ══════════════════════════════════════════════════════════════════════════════
// VISUALIZATION
// ══════════════════════════════════════════════════════════════════════════════

// Entry signals
plotshape(showSignals and buySignal, title="Buy Signal",
     location=location.belowbar, color=color.new(color.green, 0),
     style=shape.triangleup, size=size.small, text="BUY")

plotshape(showSignals and sellSignal, title="Sell Signal",
     location=location.abovebar, color=color.new(color.red, 0),
     style=shape.triangledown, size=size.small, text="SELL")

// SL/TP Lines (only when in position)
var line slLine = na
var line tpLine = na
var label slLabel = na
var label tpLabel = na

if showSLTP
    // Clean up old lines
    if not na(slLine)
        line.delete(slLine)
    if not na(tpLine)
        line.delete(tpLine)
    if not na(slLabel)
        label.delete(slLabel)
    if not na(tpLabel)
        label.delete(tpLabel)

    if strategy.position_size > 0 and not na(storedSL) and not na(storedTP)  // Long position
        slLine := line.new(bar_index - 10, storedSL, bar_index + 10, storedSL,
             color=color.red, style=line.style_dashed, width=1)
        tpLine := line.new(bar_index - 10, storedTP, bar_index + 10, storedTP,
             color=color.green, style=line.style_dashed, width=1)
        slLabel := label.new(bar_index + 11, storedSL, "SL",
             color=color.red, textcolor=color.white, size=size.tiny)
        tpLabel := label.new(bar_index + 11, storedTP, "TP",
             color=color.green, textcolor=color.white, size=size.tiny)

    else if strategy.position_size < 0 and not na(storedSL) and not na(storedTP)  // Short position
        slLine := line.new(bar_index - 10, storedSL, bar_index + 10, storedSL,
             color=color.red, style=line.style_dashed, width=1)
        tpLine := line.new(bar_index - 10, storedTP, bar_index + 10, storedTP,
             color=color.green, style=line.style_dashed, width=1)
        slLabel := label.new(bar_index + 11, storedSL, "SL",
             color=color.red, textcolor=color.white, size=size.tiny)
        tpLabel := label.new(bar_index + 11, storedTP, "TP",
             color=color.green, textcolor=color.white, size=size.tiny)

// Background color for trading hours
bgcolor(isValidHour and not isWeekend ? color.new(color.blue, 95) : na, title="Trading Hours")
bgcolor(not isValidATR and isValidHour ? color.new(color.orange, 95) : na, title="ATR Filter Active")

// ══════════════════════════════════════════════════════════════════════════════
// INFO PANEL
// ══════════════════════════════════════════════════════════════════════════════

if showInfo and barstate.islast
    var table infoPanel = table.new(position.top_right, 2, 10,
         bgcolor=color.new(color.black, 80), border_width=1)

    // Header
    table.cell(infoPanel, 0, 0, "RSI v3.7", text_color=color.white,
         text_size=size.normal, bgcolor=color.new(color.blue, 50))
    table.cell(infoPanel, 1, 0, "GBPUSD H1", text_color=color.white,
         text_size=size.normal, bgcolor=color.new(color.blue, 50))

    // RSI
    rsiColor = rsi < rsiOversold ? color.green : (rsi > rsiOverbought ? color.red : color.gray)
    table.cell(infoPanel, 0, 1, "RSI", text_color=color.gray, text_size=size.small)
    table.cell(infoPanel, 1, 1, str.tostring(rsi, "#.0"), text_color=rsiColor, text_size=size.small)

    // ATR Percentile
    atrColor = isValidATR ? color.green : color.orange
    table.cell(infoPanel, 0, 2, "ATR %", text_color=color.gray, text_size=size.small)
    table.cell(infoPanel, 1, 2, str.tostring(atrPct, "#.0") + "%", text_color=atrColor, text_size=size.small)

    // TP Multiplier
    table.cell(infoPanel, 0, 3, "TP Mult", text_color=color.gray, text_size=size.small)
    table.cell(infoPanel, 1, 3, str.tostring(tpMult, "#.00") + "x", text_color=color.white, text_size=size.small)

    // Current Hour
    hourColor = isValidHour ? color.green : color.red
    table.cell(infoPanel, 0, 4, "Hour (UTC)", text_color=color.gray, text_size=size.small)
    table.cell(infoPanel, 1, 4, str.tostring(currentHour), text_color=hourColor, text_size=size.small)

    // Status
    statusText = isWeekend ? "WEEKEND" : (not isValidHour ? "OFF HOURS" : (not isValidATR ? "ATR FILTER" : "ACTIVE"))
    statusColor = canTrade ? color.green : color.orange
    table.cell(infoPanel, 0, 5, "Status", text_color=color.gray, text_size=size.small)
    table.cell(infoPanel, 1, 5, statusText, text_color=statusColor, text_size=size.small)

    // Position
    posText = strategy.position_size > 0 ? "LONG" : (strategy.position_size < 0 ? "SHORT" : "FLAT")
    posColor = strategy.position_size > 0 ? color.green : (strategy.position_size < 0 ? color.red : color.gray)
    table.cell(infoPanel, 0, 6, "Position", text_color=color.gray, text_size=size.small)
    table.cell(infoPanel, 1, 6, posText, text_color=posColor, text_size=size.small)

// ══════════════════════════════════════════════════════════════════════════════
// RSI INDICATOR (Separate Pane)
// ══════════════════════════════════════════════════════════════════════════════

// Plot RSI in separate indicator
// Uncomment below to add RSI subplot
// indicator("RSI v3.7 - RSI Panel", shorttitle="RSI Panel", overlay=false)
// plot(rsi, "RSI", color=color.purple, linewidth=2)
// hline(rsiOversold, "Oversold", color=color.green, linestyle=hline.style_dashed)
// hline(rsiOverbought, "Overbought", color=color.red, linestyle=hline.style_dashed)
// hline(50, "Mid", color=color.gray, linestyle=hline.style_dotted)

// ══════════════════════════════════════════════════════════════════════════════
// ALERTS
// ══════════════════════════════════════════════════════════════════════════════

alertcondition(buySignal, title="RSI v3.7 BUY", message="RSI v3.7: BUY Signal on {{ticker}} @ {{close}}")
alertcondition(sellSignal, title="RSI v3.7 SELL", message="RSI v3.7: SELL Signal on {{ticker}} @ {{close}}")
alertcondition(buySignal or sellSignal, title="RSI v3.7 Entry", message="RSI v3.7: {{strategy.order.action}} Signal on {{ticker}}")

Overview

RSI Mean Reversion combines a relative strength index mean reversion model with ATR percentile thresholds to filter trades based on volatility conditions. Trading activities are controlled via configurable UTC time windows and skip hours. Dynamic take profit targets adapt to market volatility levels and specific time bonuses, while stop losses are placed using an ATR multiplier. Trades are forcibly closed after reaching a maximum bar holding period.

Details

Published by

GifariKemal on GitHub

Code credit

SURIOTA Trading Team, as written in the script's header

Type

Strategy, Pine Script v5

Draws

On the price chart

Category

Oscillators & momentum, Volatility & bands, Mean reversion, Sessions & time

Markets

Forex

Timeframes

M15-H1

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Topics

algorithmic-tradingforexhmmictkalman-filtermetatrader5pythonquantitative-financesmart-money-conceptstrading-bot

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
RSI Periodint10rsiPeriod
RSI Oversoldint42rsiOversold
RSI Overboughtint58rsiOverbought
ATR Periodint14atrPeriod
ATR Percentile Lookbackint100atrLookback
Min ATR Percentilefloat20.0atrMinPct
Max ATR Percentilefloat80.0atrMaxPct
SL Multiplier (ATR)float1.5slMult
TP Low Volatilityfloat2.4tpLow
TP Medium Volatilityfloat3.0tpMed
TP High Volatilityfloat3.6tpHigh
TP Time Bonus (12-16 UTC)float0.35tpTimeBonus
Show all 21 settings
SettingTypeDefaultVariable
Trading Start Hour (UTC)int7tradingStartHour
Trading End Hour (UTC)int22tradingEndHour
Skip Hour (UTC)int12skipHour
TP Bonus Start Hourint12tpBonusStart
TP Bonus End Hourint16tpBonusEnd
Max Holding (Bars)int46maxHoldingBars
Show Entry SignalsbooltrueshowSignals
Show SL/TP LinesbooltrueshowSLTP
Show Info PanelbooltrueshowInfo
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

13 stars

This strategy overlays the price chart to trade DAX futures during European market hours. It calculates exponential and simple moving averages, RSI, MACD, Bollinger Bands, ATR, and realized volatility to simulate VIX levels. Trades execute when session time fi

Strategy v5 Sessions & timeOscillators & momentum
Updated 8 months ago · Custom licence GitHub repository of ClaudeHedge - DAX Session Strategy
2 stars

This mean-reversion trading strategy targets XAUUSD during the Asian trading session. It generates buy signals when price touches the lower Bollinger Band and RSI is oversold, and sell signals when price reaches the upper band and RSI is overbought. Trades are

Strategy v6 Mean reversionSessions & time
Updated 4 months ago · None stated GitHub repository of XAUUSD Mean Reversion Strategy
330 stars

Alert BB + RSI [Alorse] is a multi-symbol mean-reversion strategy designed for cryptocurrency trading. It evaluates Bollinger Bands and the Relative Strength Index (RSI) across up to 20 user-defined ticker symbols simultaneously. The strategy enters long posit

Strategy v4 Oscillators & momentumVolatility & bands
Updated 2 months ago · None stated GitHub repository of Alert BB + RSI [Alorse]
106 stars

A multi-timeframe Dollar Cost Averaging (DCA) trading strategy that combines signals from three RSI indicators, three CCI indicators, and Bollinger Bands. It places up to five weighted scale-in safety orders to lower the average entry price. The script offers

Strategy v5 Oscillators & momentumVolatility & bands
Updated 2 years ago · GPL-3.0 GitHub repository of 3RSI 3CCI BB 5orders DCA Strategy+
104 stars

This strategy combines Bollinger Bands with a short RSI to fade moves that leave the bands and then return. A long requires the prior bar below the lower band and oversold, then a close back inside the band while RSI is between 30 and 50. Shorts mirror that at

Strategy v5 Oscillators & momentumVolatility & bands
Updated 3 years ago · MIT GitHub repository of Bollinger Bands and RSI Strategy

More strategys

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers