Double VWAP Oscillation Breakthrough Strategy
The double VWAP oscillation breakthrough strategy analyzes market trends using double VWAP bands and seeks breakthrough opportunities in oscillating markets. It combines the ADX indicator to determine if the market…

Overview
The double VWAP oscillation breakthrough strategy analyzes market trends using double VWAP bands and seeks breakthrough opportunities in oscillating markets. It combines the ADX indicator to determine if the market is oscillating and utilizes two VWAP bands of different standard deviations to identify breakout entry points.
Strategy Principles
The strategy consists of the following main components:
VWAP Settings: Calculate VWAP bands and their width. The inner VWAP width is controlled by
stDevMultiplier, default to 1. The outer VWAP width is controlled bystDevMultiplier, default to 2.ADX Settings: Calculate ADX values to determine if the market is oscillating. The market is considered oscillating when ADX is below the threshold. ADX parameters are configurable.
Entry Settings: Enter the market when prices break through the outer VWAP bands during oscillation. Stop loss price and take profit prices are configurable.
Limit Entries: Optional EMA or time frame filters to avoid entering during unfavorable time periods.
Profit Taking: Close positions when tracking stop loss or take profit prices are breached. Option to exit when prices break the outer VWAP bands.
The strategy identifies oscillating markets using the ADX indicator and seeks entry opportunities when prices break VWAP bands. The double VWAP bands provide additional filters to ensure strong entry signals. The trailing stop locks in profits in a more stable way.
Advantages Analysis
The double VWAP bands provide extra filters for stronger entry signals.
The ADX oscillator identifies oscillation and avoids wrong entries during trends.
The trailing stop locks in profits and prevents being trapped.
Highly configurable parameters adapt to different market conditions.
Simple logic makes it easy to understand, replicate and modify.
Risks and Solutions
Improper parameter tuning can lead to over-eager entry and exits. Optimize parameter combinations to ensure strategy stability.
Trailing stops can be too aggressive or conservative. Dynamically adjust based on volatility indicators.
Performance depends heavily on trading sessions. Optimize using time filters to capture efficiency.
VWAP bands sensitive to erratic prices. Confirm prices with additional indicators.
Optimization Directions
Dynamically adjust stop loss ranges based on volatility and other metrics.
Add higher timeframe trend and institution signals to avoid counter-trend entries.
Consider position sizing based on volatility and total capital.
Test different VWAP anchor periods. VWAP periods determine overall strategy holding period.
Summary
The double VWAP oscillation breakout strategy identifies oscillation with ADX and provides additional entry filters with the VWAP bands. The logic is simple to implement. Parameters tuning, stop loss optimization and position sizing can significantly improve stability.
Parameters
| Name | Default | What it controls |
|---|---|---|
| v_input_bool_1 | false | Close early if price crosses outer VWAP band |
| v_input_string_1 | 0 | (?VWAP Settings)Anchor Period: Session | Week | Month | Quarter | Year |
| v_input_1_close | 0 | Inner VWAP Source: close | high | low | open | hl2 | hlc3 | hlcc4 | ohlc4 |
| v_input_2 | true | Inner Bands Multiplier |
| v_input_3 | 2 | Outer Bands Multiplier |
| v_input_int_1 | 14 | (?ADX Settings)ADX Smoothing |
| v_input_int_2 | 14 | DI Length |
| v_input_int_3 | 40 | ADX Threshold |
| v_input_float_1 | 2 | (?Entry Settings)Stop Loss (%) |
| v_input_float_2 | 6 | Take Profit (%) |
| v_input_int_4 | true | Long Entry Limit Lookback |
| v_input_int_5 | true | Short Entry Limit Lookback |
| v_input_float_3 | 3 | Start Trailing After (%) |
| v_input_float_4 | 2 | Trail Behind (%) |
| v_input_bool_2 | true | (?Limit Entries)Use EMA Filter |
| v_input_timeframe_1 | Timeframe | |
| v_input_int_6 | 300 | Length |
| v_input_source_1_hl2 | 0 | Source: hl2 | high | low | open | close | hlc3 | hlcc4 | ohlc4 |
| v_input_bool_3 | false | Use Time Session Filter |
Source Pine Script
/*backtest
start: 2023-10-23 00:00:00
end: 2023-11-22 00:00:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © jordanfray
//@version=5
strategy(title="Double VWAP Strategy", overlay=true, scale=scale.none, max_bars_back=500, default_qty_type=strategy.percent_of_equity, default_qty_value=100,initial_capital=100000, commission_type=strategy.commission.percent, commission_value=0.05, backtest_fill_limits_assumption=2)
// Indenting Classs
indent_1 = " "
indent_2 = " "
indent_3 = " "
indent_4 = " "
// Group Titles
group_one_title = "VWAP Settings"
group_two_title = "ADX Settings"
group_three_title = "Entry Settings"
group_four_title = "Limit Entries"
// Input Tips
adx_thresholdToolTip = "The minumn ADX value to allow opening a postion"
adxCancelToolTip= "You can optionally set a different lower value for ADX that will allow entries even if below the trigger threshold."
ocean_blue = color.new(#0C6090,0)
sky_blue = color.new(#00A5FF,0)
green = color.new(#2DBD85,0)
red = color.new(#E02A4A,0)
light_blue = color.new(#00A5FF,90)
light_green = color.new(#2DBD85,90)
light_red = color.new(#E02A4A,90)
light_yellow = color.new(#FFF900,90)
white = color.new(#ffffff,0)
transparent = color.new(#000000,100)
// Strategy Settings - VWAP
var cumVol = 0.
cumVol += nz(volume)
if barstate.islast and cumVol == 0
runtime.error("No volume is provided by the data vendor.")
computeVWAP(src, isNewPeriod, stDevMultiplier) =>
var float sum_src_vol = na
var float sum_vol = na
var float sum_src_src_vol = na
sum_src_vol := isNewPeriod ? src * volume : src * volume + sum_src_vol[1]
sum_vol := isNewPeriod ? volume : volume + sum_vol[1]
sum_src_src_vol := isNewPeriod ? volume * math.pow(src, 2) : volume * math.pow(src, 2) + sum_src_src_vol[1]
_vwap = sum_src_vol / sum_vol
variance = sum_src_src_vol / sum_vol - math.pow(_vwap, 2)
variance := variance < 0 ? 0 : variance
standard_deviation = math.sqrt(variance)
lower_band_value = _vwap - standard_deviation * stDevMultiplier
upper_band_value = _vwap + standard_deviation * stDevMultiplier
[_vwap, lower_band_value, upper_band_value]
var anchor = input.string(defval="Session", title="Anchor Period", options=["Session", "Week", "Month", "Quarter", "Year"], group=group_one_title)
src = input(defval = close, title = "Inner VWAP Source", group=group_one_title)
multiplier_inner = input(defval=1.0, title="Inner Bands Multiplier", group=group_one_title)
multiplier_outer = input(defval=2.0, title="Outer Bands Multiplier", group=group_one_title)
show_bands = true
timeChange(period) =>
ta.change(time(period))
isNewPeriod = switch anchor
"Session" => timeChange("D")
"Week" => timeChange("W")
"Month" => timeChange("M")
"Quarter" => timeChange("3M")
"Year" => timeChange("12M")
=> false
float vwap_val = na
float upper_inner_band_value = na
float lower_inner_band_value = na
float upper_outer_band_value = na
float lower_outer_band_value = na
[inner_vwap, inner_bottom, inner_top] = computeVWAP(src, isNewPeriod, multiplier_inner)
[outer_vwap, outer_bottom, outer_top] = computeVWAP(src, isNewPeriod, multiplier_outer)
vwap_val := inner_vwap
upper_inner_band_value := show_bands ? inner_top : na
lower_inner_band_value := show_bands ? inner_bottom : na
upper_outer_band_value := show_bands ? outer_top : na
lower_outer_band_value := show_bands ? outer_bottom : na
plot(vwap_val, title="VWAP", color=green)
upper_inner_band = plot(upper_inner_band_value, title="Upper Inner Band", color=sky_blue)
lower_inner_band = plot(lower_inner_band_value, title="Lower Inner Band", color=sky_blue)
upper_outer_band = plot(upper_outer_band_value, title="Upper Outer Band", linewidth=2, color=ocean_blue)
lower_outer_band = plot(lower_outer_band_value, title="Lower Outer Band", linewidth=2, color=ocean_blue)
fill(upper_outer_band, lower_outer_band, title="VWAP Bands Fill", color= show_bands ? light_blue : na)
// ADX Settings
adx_len = input.int(defval=14, title="ADX Smoothing", group=group_two_title)
di_len = input.int(defval=14, title="DI Length", group=group_two_title)
adx_threshold = input.int(defval=40, title="ADX Threshold", group=group_two_title, tooltip=adx_thresholdToolTip)
dirmov(len) =>
up = ta.change(high)
down = -ta.change(low)
plus_dm = na(up) ? na : (up > down and up > 0 ? up : 0)
minus_dm = na(down) ? na : (down > up and down > 0 ? down : 0)
true_range = ta.rma(ta.tr, len)
plus = fixnan(100 * ta.rma(plus_dm, len) / true_range)
minus = fixnan(100 * ta.rma(minus_dm, len) / true_range)
[plus, minus]
adx(di_len, adx_len) =>
[plus, minus] = dirmov(di_len)
sum = plus + minus
adx = 100 * ta.rma(math.abs(plus - minus) / (sum == 0 ? 1 : sum), adx_len)
adx_val = adx(di_len, adx_len)
plot(adx_val, title="ADX")
// Entry Settings
stop_loss_val = input.float(defval=2.0, title="Stop Loss (%)", step=0.1, group=group_three_title)/100
take_profit_val = input.float(defval=6.0, title="Take Profit (%)", step=0.1, group=group_three_title)/100
long_entry_limit_lookback = input.int(defval=1, title="Long Entry Limit Lookback", minval=1, step=1, group=group_three_title)
short_entry_limit_lookback = input.int(defval=1, title="Short Entry Limit Lookback", minval=1, step=1, group=group_three_title)
limit_order_long_price = ta.lowest(close, long_entry_limit_lookback)
limit_order_short_price = ta.highest(close, short_entry_limit_lookback)
start_trailing_after = input.float(defval=3, title="Start Trailing After (%)", step=0.1, group=group_three_title)/100
trail_behind = input.float(defval=2, title="Trail Behind (%)", step=0.1, group=group_three_title)/100
close_early_if_crosses_outter_band = input.bool(defval=false, title="Close early if price crosses outer VWAP band")
// Limit Entries
enableEmaFilter = input.bool(defval=true, title="Use EMA Filter", group=group_four_title)
emaFilterTimeframe = input.timeframe(defval="", title=indent_4+"Timeframe", group=group_four_title)
emaFilterLength = input.int(defval=300, minval=1, step=10, title=indent_4+"Length", group=group_four_title)
emaFilterSource = input.source(defval=hl2, title=indent_4+"Source", group=group_four_title)
ema_filter = ta.ema(emaFilterSource, emaFilterLength)
ema_filter_smoothed = request.security(syminfo.tickerid, emaFilterTimeframe, ema_filter[barstate.isrealtime ? 1 : 0], gaps=barmerge.gaps_on)
plot(enableEmaFilter ? ema_filter_smoothed: na, title="EMA Macro Filter", linewidth=2, color=sky_blue, editable=true)
useTimeFilter = input.bool(defval=false, title="Use Time Session Filter", group=group_four_title)
withinTime = true
long_start_trailing_val = strategy.position_avg_price + (strategy.position_avg_price * start_trailing_after)
short_start_trailing_val = strategy.position_avg_price - (strategy.position_avg_price * start_trailing_after)
long_trail_behind_val = close - (strategy.position_avg_price * (trail_behind/100))
short_trail_behind_val = close + (strategy.position_avg_price * (trail_behind/100))
currently_in_a_long_postion = strategy.position_size > 0
currently_in_a_short_postion = strategy.position_size < 0
long_profit_target = strategy.position_avg_price * (1 + take_profit_val)
long_stop_loss = strategy.position_avg_price * (1.0 - stop_loss_val)
short_profit_target = strategy.position_avg_price * (1 - take_profit_val)
short_stop_loss = strategy.position_avg_price * (1 + stop_loss_val)
bars_since_entry = currently_in_a_long_postion or currently_in_a_short_postion ? bar_index - strategy.opentrades.entry_bar_index(strategy.opentrades - 1) + 1 : 5
plot(bars_since_entry, editable=false, title="Bars Since Entry", color=green)
long_run_up = ta.highest(high, bars_since_entry)
long_trailing_stop = currently_in_a_long_postion and bars_since_entry > 0 and long_run_up > long_start_trailing_val ? long_run_up - (long_run_up * trail_behind) : long_stop_loss
//long_run_up_line = plot(long_run_up, style=plot.style_stepline, editable=false, color=currently_in_a_long_postion ? green : transparent)
long_trailing_stop_line = plot(long_trailing_stop, style=plot.style_stepline, editable=false, color=currently_in_a_long_postion ? long_trailing_stop > strategy.position_avg_price ? green : red : transparent)
short_run_up = ta.lowest(low, bars_since_entry)
short_trailing_stop = currently_in_a_short_postion and bars_since_entry > 0 and short_run_up < short_start_trailing_val ? short_run_up + (short_run_up * trail_behind) : short_stop_loss
//short_run_up_line = plot(short_run_up, style=plot.style_stepline, editable=false, color=currently_in_a_short_postion ? green : transparent)
short_trailing_stop_line = plot(short_trailing_stop, style=plot.style_stepline, editable=false, color=currently_in_a_short_postion ? short_trailing_stop < strategy.position_avg_price ? green : red : transparent)
// Conditions
adx_is_below_threshold = adx_val < adx_threshold
price_crossed_down_VWAP_lower_outer_band = ta.crossunder(low, lower_outer_band_value)
price_closed_above_VWAP_lower_outer_band = close > lower_outer_band_value
price_crossed_up_VWAP_upper_outer_band = ta.crossover(high,upper_outer_band_value)
price_closed_below_VWAP_upper_outer_band = close < upper_outer_band_value
price_above_ema_filter = close > ema_filter_smoothed
price_below_ema_filter = close < ema_filter_smoothed
//Trade Restirctions
no_trades_allowed = not withinTime or not adx_is_below_threshold
// Enter trades when...
long_conditions_met = enableEmaFilter ? price_above_ema_filter and not currently_in_a_long_postion and withinTime and adx_is_below_threshold and price_crossed_down_VWAP_lower_outer_band and price_closed_above_VWAP_lower_outer_band : not currently_in_a_long_postion and withinTime and adx_is_below_threshold and price_crossed_down_VWAP_lower_outer_band and price_closed_above_VWAP_lower_outer_band
short_conditions_met = enableEmaFilter ? price_below_ema_filter and not currently_in_a_short_postion and withinTime and adx_is_below_threshold and price_crossed_up_VWAP_upper_outer_band and price_closed_below_VWAP_upper_outer_band : not currently_in_a_short_postion and withinTime and adx_is_below_threshold and price_crossed_up_VWAP_upper_outer_band and price_closed_below_VWAP_upper_outer_band
plotshape(long_conditions_met ? close : na, title="Long Entry Symbol", color=green, style=shape.triangleup, location=location.abovebar)
plotshape(short_conditions_met ? close : na, title="Short Entry Symbol", color=red, style=shape.triangledown, location=location.belowbar)
// Take Profit When...
price_closed_below_short_trailing_stop = ta.cross(close, short_trailing_stop)
price_hit_short_entry_profit_target = low > short_profit_target
price_closed_above_long_entry_trailing_stop = ta.cross(close, long_trailing_stop)
price_hit_long_entry_profit_target = high > long_profit_target
long_position_take_profit = close_early_if_crosses_outter_band ? price_crossed_up_VWAP_upper_outer_band or price_closed_above_long_entry_trailing_stop or price_hit_long_entry_profit_target : price_closed_above_long_entry_trailing_stop or price_hit_long_entry_profit_target
short_position_take_profit = close_early_if_crosses_outter_band ? price_crossed_down_VWAP_lower_outer_band or price_closed_below_short_trailing_stop or price_hit_short_entry_profit_target : price_closed_below_short_trailing_stop or price_hit_short_entry_profit_target
// Cancel limir order if...
cancel_long_condition = false
cancel_short_condition = false
// Long Entry
strategy.entry(id="Long", direction=strategy.long, limit=limit_order_long_price, when=long_conditions_met)
strategy.cancel(id="Cancel Long", when=cancel_long_condition)
strategy.exit(id="Close Long", from_entry="Long", stop=long_trailing_stop, limit=long_profit_target, when=long_position_take_profit)
// Short Entry
strategy.entry(id="Short", direction=strategy.short, limit=limit_order_short_price, when=short_conditions_met)
strategy.cancel(id="Cancel Short", when=cancel_short_condition)
strategy.exit(id="Close Short", from_entry="Short", stop=short_trailing_stop, limit=short_profit_target, when=short_position_take_profit)
entry = plot(strategy.position_avg_price, editable=false, title="Entry", style=plot.style_stepline, color=currently_in_a_long_postion or currently_in_a_short_postion ? color.blue : transparent, linewidth=1)
fill(entry,long_trailing_stop_line, editable=false, color=currently_in_a_long_postion ? long_trailing_stop > strategy.position_avg_price ? light_green : light_red : transparent)
fill(entry,short_trailing_stop_line, editable=false, color=currently_in_a_short_postion ? short_trailing_stop < strategy.position_avg_price ? light_green : light_red : transparent)
bgcolor(title="No Trades Allowed", color=no_trades_allowed ? light_red : light_green)
Published by ChaoZhang on the FMZ strategy square · last modified 2023-11-23. Collected in the Quant Trading Vault. Unvalidated research: re-test with your own data and costs before trusting it.
Build this with AgenKit
A spec is not a trading system. Paste this into your coding agent and AgenKit runs a specialist team — architecture, test-first build, risk gates, review — with paper trading on by default.
/agenkit build a production-grade trading bot with a backtest engine, risk controls and paper trading by default from strategies/Double-VWAP-Oscillation-Breakthrough-Strategy.md- npx agenkit install engineering-kit in your project
- Paste the command into your agent and approve each phase
- Get tests, backtest, risk controls and a paper-trading switch
Understand it first
Before you run this, read the lesson behind the idea and how to backtest it honestly. Specs like this one are the survivors of an unknown number of trials; deflate their results accordingly.
Related specs
- Multi Filter Trend Breakthrough Smart Moving Average Trading StrategyPine Script · Breakout · 1h
- Dynamic Multi indicator Quantitative Trading StrategyPine Script · Breakout · 1d
- Dual Breakout Volatility Channel StrategyPine Script · Breakout · 1h
- Dynamic Dual Moving Average Breakthrough Trading System Dynamic Dual Moving Average Breakthrough Trading SystemPine Script · Breakout · 1d
- Momentum Breakout Trading Strategy 440964Pine Script · Breakout · 5m
- Moving Average Crossover and Upper Rail Breakthrough Impulse StrategyPine Script · Breakout · 1m