Skip to content

Instantly share code, notes, and snippets.

@boskiv
Last active February 22, 2026 16:48
Show Gist options
  • Select an option

  • Save boskiv/24c61b71c80aeb36fd65eb822ac454ff to your computer and use it in GitHub Desktop.

Select an option

Save boskiv/24c61b71c80aeb36fd65eb822ac454ff to your computer and use it in GitHub Desktop.
Rolling VWAP RSI Strategy
//@version=6
strategy(
title = "Rolling VWAP Mean Reversion Grid",
shorttitle = "VWAP MR Grid",
overlay = true,
initial_capital = 10000,
default_qty_type = strategy.fixed,
default_qty_value = 0,
commission_type = strategy.commission.percent,
commission_value = 0.04,
slippage = 1,
pyramiding = 10)
// ── Inputs ──────────────────────────────────────────────────────────
string GRP_RSI = "RSI Filter"
string GRP_GRID = "Grid & Martingale"
string GRP_EXIT = "Exit Settings"
string GRP_RISK = "Risk Management"
bool useRsiFilter = input.bool(defval = true, title = "Use RSI Filter (1st grid level)", group = GRP_RSI)
int rsiLength = input.int(defval = 14, title = "RSI Length", minval = 2, group = GRP_RSI)
float rsiOversold = input.float(defval = 30.0, title = "RSI Oversold", minval = 0, maxval = 50, group = GRP_RSI)
float rsiOverbought = input.float(defval = 70.0, title = "RSI Overbought", minval = 50, maxval = 100, group = GRP_RSI)
float baseQty = input.float(defval = 1.0, title = "Base Quantity (contracts/shares)", minval = 0.001, group = GRP_GRID,
tooltip = "Size of the first grid entry. Each subsequent level is multiplied by the Martingale factor")
int gridLevels = input.int(defval = 10, title = "Grid Levels", minval = 2, maxval = 10, group = GRP_GRID,
tooltip = "Number of entries from ±3σ deeper. Must match pyramiding (10)")
float martinMult = input.float(defval = 1.5, title = "Martingale Multiplier", minval = 1.0, step = 0.1, group = GRP_GRID,
tooltip = "Each grid level qty = base × multiplier ^ level. 1.0 = flat sizing, 2.0 = classic martingale")
bool showGrid = input.bool(defval = true, title = "Show Grid Levels on Chart", group = GRP_GRID)
string exitTarget = input.string(defval = "VWAP", title = "Take-Profit Target", options = ["VWAP", "+1 Sigma", "+2 Sigma"], group = GRP_EXIT,
tooltip = "Where to take profit on the reversion move")
bool useStopLoss = input.bool(defval = true, title = "Use Stop Loss", group = GRP_RISK)
float stopMultiplier = input.float(defval = 1.0, title = "Stop Loss (× σ beyond deepest grid level)", minval = 0.1, step = 0.1, group = GRP_RISK,
tooltip = "Stop is placed this many σ beyond the deepest grid level")
// ── Window calculations ─────────────────────────────────────────────
const int MINUTES_IN_DAY = 1440
var int period = timeframe.multiplier
var int fastWindow = MINUTES_IN_DAY / period
var int slowWindow = fastWindow * 7
if timeframe.isintraday == false
fastWindow := 28 * period
slowWindow := 84 * period
// ── Slow VWAP + StDev ──────────────────────────────────────────────
float vwap = ta.vwma(close, slowWindow)
float stdev = ta.stdev(close, slowWindow)
float plus1 = vwap + stdev
float plus2 = vwap + 2 * stdev
float plus3 = vwap + 3 * stdev
float minus1 = vwap - stdev
float minus2 = vwap - 2 * stdev
float minus3 = vwap - 3 * stdev
// ── RSI ─────────────────────────────────────────────────────────────
float rsiValue = ta.rsi(close, rsiLength)
bool rsiLongOk = useRsiFilter ? rsiValue <= rsiOversold : true
bool rsiShortOk = useRsiFilter ? rsiValue >= rsiOverbought : true
// ── Grid calculations ───────────────────────────────────────────────
float gridWidth = math.abs(minus3 - minus2) // = 1σ
float longGridStep = gridWidth / (gridLevels - 1)
float shortGridStep = gridWidth / (gridLevels - 1)
// ── Grid state tracking ─────────────────────────────────────────────
var int longFilled = 0
var int shortFilled = 0
var float fixedLongSL = na
var float fixedShortSL = na
if strategy.position_size == 0
longFilled := 0
shortFilled := 0
fixedLongSL := na
fixedShortSL := na
// ── Grid entries with Martingale ────────────────────────────────────
float calcLongSL = minus3 - gridWidth - stdev * stopMultiplier
float calcShortSL = plus3 + gridWidth + stdev * stopMultiplier
for i = 0 to gridLevels - 1
if longFilled == i and strategy.position_size >= 0
float lvl = minus3 - longGridStep * i
float qty = baseQty * math.pow(martinMult, i)
bool rsiOk = i == 0 ? rsiLongOk : true
if close <= lvl and rsiOk
strategy.entry("L" + str.tostring(i), strategy.long, qty = qty)
if i == 0
fixedLongSL := calcLongSL
longFilled += 1
for i = 0 to gridLevels - 1
if shortFilled == i and strategy.position_size <= 0
float lvl = plus3 + shortGridStep * i
float qty = baseQty * math.pow(martinMult, i)
bool rsiOk = i == 0 ? rsiShortOk : true
if close >= lvl and rsiOk
strategy.entry("S" + str.tostring(i), strategy.short, qty = qty)
if i == 0
fixedShortSL := calcShortSL
shortFilled += 1
// ── Exit logic ──────────────────────────────────────────────────────
float longTP = switch exitTarget
"VWAP" => vwap
"+1 Sigma" => minus1
"+2 Sigma" => minus2
=> vwap
float shortTP = switch exitTarget
"VWAP" => vwap
"+1 Sigma" => plus1
"+2 Sigma" => plus2
=> vwap
if useStopLoss and strategy.position_size > 0 and not na(fixedLongSL)
strategy.exit("SL Long", from_entry = "L0", stop = fixedLongSL)
if longFilled > 1
strategy.exit("SL L1", from_entry = "L1", stop = fixedLongSL)
if longFilled > 2
strategy.exit("SL L2", from_entry = "L2", stop = fixedLongSL)
if longFilled > 3
strategy.exit("SL L3", from_entry = "L3", stop = fixedLongSL)
if longFilled > 4
strategy.exit("SL L4", from_entry = "L4", stop = fixedLongSL)
if longFilled > 5
strategy.exit("SL L5", from_entry = "L5", stop = fixedLongSL)
if longFilled > 6
strategy.exit("SL L6", from_entry = "L6", stop = fixedLongSL)
if longFilled > 7
strategy.exit("SL L7", from_entry = "L7", stop = fixedLongSL)
if longFilled > 8
strategy.exit("SL L8", from_entry = "L8", stop = fixedLongSL)
if longFilled > 9
strategy.exit("SL L9", from_entry = "L9", stop = fixedLongSL)
if useStopLoss and strategy.position_size < 0 and not na(fixedShortSL)
strategy.exit("SL Short", from_entry = "S0", stop = fixedShortSL)
if shortFilled > 1
strategy.exit("SL S1", from_entry = "S1", stop = fixedShortSL)
if shortFilled > 2
strategy.exit("SL S2", from_entry = "S2", stop = fixedShortSL)
if shortFilled > 3
strategy.exit("SL S3", from_entry = "S3", stop = fixedShortSL)
if shortFilled > 4
strategy.exit("SL S4", from_entry = "S4", stop = fixedShortSL)
if shortFilled > 5
strategy.exit("SL S5", from_entry = "S5", stop = fixedShortSL)
if shortFilled > 6
strategy.exit("SL S6", from_entry = "S6", stop = fixedShortSL)
if shortFilled > 7
strategy.exit("SL S7", from_entry = "S7", stop = fixedShortSL)
if shortFilled > 8
strategy.exit("SL S8", from_entry = "S8", stop = fixedShortSL)
if shortFilled > 9
strategy.exit("SL S9", from_entry = "S9", stop = fixedShortSL)
if strategy.position_size > 0 and close >= longTP
strategy.close_all(comment = "TP @ " + exitTarget)
if strategy.position_size < 0 and close <= shortTP
strategy.close_all(comment = "TP @ " + exitTarget)
// ── Plot fixed SL level ─────────────────────────────────────────────
plot(strategy.position_size > 0 ? fixedLongSL : strategy.position_size < 0 ? fixedShortSL : na,
"Stop Loss", color.new(color.red, 0), 2, plot.style_cross)
// ── Plots: VWAP bands ──────────────────────────────────────────────
color VWAP_COL = color.rgb(13, 157, 219, 0)
color VWAP_COL_BAND = color.rgb(13, 157, 219, 50)
plot(vwap, "VWAP", VWAP_COL)
plot(plus1, "VWAP + σ", VWAP_COL_BAND)
plot(plus2, "VWAP + 2σ", VWAP_COL_BAND)
plot(plus3, "VWAP + 3σ", VWAP_COL_BAND)
plot(minus1, "VWAP − σ", VWAP_COL_BAND)
plot(minus2, "VWAP − 2σ", VWAP_COL_BAND)
plot(minus3, "VWAP − 3σ", VWAP_COL_BAND)
// ── Plots: Grid levels ─────────────────────────────────────────────
color GRID_LONG_COL = color.new(color.green, 30)
color GRID_SHORT_COL = color.new(color.red, 30)
plot(showGrid ? minus3 - longGridStep * 0 : na, "Grid L0", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 1 : na, "Grid L1", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 2 : na, "Grid L2", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 3 : na, "Grid L3", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 4 : na, "Grid L4", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 5 : na, "Grid L5", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 6 : na, "Grid L6", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 7 : na, "Grid L7", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 8 : na, "Grid L8", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? minus3 - longGridStep * 9 : na, "Grid L9", GRID_LONG_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 0 : na, "Grid S0", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 1 : na, "Grid S1", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 2 : na, "Grid S2", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 3 : na, "Grid S3", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 4 : na, "Grid S4", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 5 : na, "Grid S5", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 6 : na, "Grid S6", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 7 : na, "Grid S7", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 8 : na, "Grid S8", GRID_SHORT_COL, 1, plot.style_circles)
plot(showGrid ? plus3 + shortGridStep * 9 : na, "Grid S9", GRID_SHORT_COL, 1, plot.style_circles)
// ── RSI subplot ─────────────────────────────────────────────────────
plot(rsiValue, "RSI", color.rgb(160, 120, 255), display = display.pane)
hline(rsiOverbought, "Overbought", color.new(color.red, 60), linestyle = hline.style_dotted)
hline(rsiOversold, "Oversold", color.new(color.green, 60), linestyle = hline.style_dotted)
// ── Info table ──────────────────────────────────────────────────────
var table infoTbl = table.new(position.top_right, 2, 4, bgcolor = color.new(color.black, 80), border_width = 1)
if barstate.islast
float totalQty = 0.0
for i = 0 to gridLevels - 1
totalQty += baseQty * math.pow(martinMult, i)
table.cell(infoTbl, 0, 0, "Grid Levels", text_color = color.white, text_size = size.small)
table.cell(infoTbl, 1, 0, str.tostring(gridLevels), text_color = color.yellow, text_size = size.small)
table.cell(infoTbl, 0, 1, "Martingale ×", text_color = color.white, text_size = size.small)
table.cell(infoTbl, 1, 1, str.tostring(martinMult, "#.##"), text_color = color.yellow, text_size = size.small)
table.cell(infoTbl, 0, 2, "Max Total Qty", text_color = color.white, text_size = size.small)
table.cell(infoTbl, 1, 2, str.tostring(totalQty, "#.##"), text_color = color.orange, text_size = size.small)
table.cell(infoTbl, 0, 3, "Levels Filled", text_color = color.white, text_size = size.small)
string filledStr = strategy.position_size > 0 ? str.tostring(longFilled) : strategy.position_size < 0 ? str.tostring(shortFilled) : "0"
table.cell(infoTbl, 1, 3, filledStr + " / " + str.tostring(gridLevels), text_color = color.aqua, text_size = size.small)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment