Last active
February 22, 2026 16:48
-
-
Save boskiv/24c61b71c80aeb36fd65eb822ac454ff to your computer and use it in GitHub Desktop.
Rolling VWAP RSI Strategy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //@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