Home
/
Strategy examples
View on GitHub
VWAP Williams R Strategy
The VWAP Williams %R strategy focuses on intraday reversion around the Volume Weighted Average Price. It observes when price drifts away from VWAP while the Williams %R oscillator reaches oversold or overbought territory. The assumption is that extreme readings near VWAP often lead to a snapback toward the mean.
Testing indicates an average annual return of about 40%. It performs best in the crypto market.
When the oscillator drops below -80 and price trades under VWAP, the setup implies selling pressure is fading and a rebound may follow. Conversely, a reading above -20 while price is positioned above VWAP warns that buyers are exhausted and a pullback is likely. The strategy opens trades in the direction of a potential return to VWAP and watches for that move to complete.
This approach fits active intraday traders who prefer frequent mean reversion opportunities. A small stop‑loss relative to VWAP keeps risk contained while still allowing enough room for price to fluctuate before reversing.
Details
Entry Criteria :
Long : Price < VWAP && Williams %R < -80 (oversold below VWAP)
Short : Price > VWAP && Williams %R > -20 (overbought above VWAP)
Long/Short : Both sides.
Exit Criteria :
Long : Exit long position when price breaks above VWAP
Short : Exit short position when price breaks below VWAP
Stops : Yes.
Default Values :
WilliamsRPeriod = 14
StopLossPercent = 2m
CandleType = TimeSpan.FromMinutes(5)
Filters :
Category: Mixed
Direction: Both
Indicators: VWAP Williams R
Stops: Yes
Complexity: Intermediate
Timeframe: Intraday
Seasonality: No
Neural networks: No
Divergence: No
Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on VWAP and Williams %R indicators
/// </summary>
public class VwapWilliamsRStrategy : Strategy
{
private readonly StrategyParam<int> _williamsRPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
// Store previous values
private decimal _previousWilliamsR;
private int _cooldown;
private DateTime _vwapDate;
private decimal _vwapCumPv;
private decimal _vwapCumVol;
/// <summary>
/// Williams %R period
/// </summary>
public int WilliamsRPeriod
{
get => _williamsRPeriod.Value;
set => _williamsRPeriod.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop-loss percentage
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type for strategy
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor
/// </summary>
public VwapWilliamsRStrategy()
{
_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
.SetRange(5, 50)
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators")
;
_cooldownBars = Param(nameof(CooldownBars), 60)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetRange(0.5m, 5m)
.SetDisplay("Stop-Loss %", "Stop-loss percentage from entry price", "Risk Management")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousWilliamsR = default;
_cooldown = 0;
_vwapDate = default;
_vwapCumPv = 0m;
_vwapCumVol = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicator
var williamsR = new WilliamsR { Length = WilliamsRPeriod };
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(williamsR, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, williamsR);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal williamsRValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
var date = candle.ServerTime.Date;
if (_vwapDate != date)
{
_vwapDate = date;
_vwapCumPv = 0m;
_vwapCumVol = 0m;
}
_vwapCumPv += candle.ClosePrice * candle.TotalVolume;
_vwapCumVol += candle.TotalVolume;
if (_vwapCumVol <= 0m)
return;
var vwapValue = _vwapCumPv / _vwapCumVol;
// Store previous value to detect changes
var previousWilliamsR = _previousWilliamsR;
_previousWilliamsR = williamsRValue;
var price = candle.ClosePrice;
var crossedIntoOversold = previousWilliamsR > -80m && williamsRValue <= -80m;
var crossedIntoOverbought = previousWilliamsR < -20m && williamsRValue >= -20m;
if (_cooldown > 0)
_cooldown--;
if (_cooldown == 0 && price < vwapValue * 0.999m && crossedIntoOversold && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = CooldownBars;
}
else if (_cooldown == 0 && price > vwapValue * 1.001m && crossedIntoOverbought && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import WilliamsR
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class vwap_williams_r_strategy(Strategy):
"""
Strategy based on VWAP and Williams %R indicators.
Long when price below VWAP and Williams %R crosses into oversold.
Short when price above VWAP and Williams %R crosses into overbought.
"""
def __init__(self):
super(vwap_williams_r_strategy, self).__init__()
self._williams_r_period = self.Param("WilliamsRPeriod", 14) \
.SetRange(5, 50) \
.SetDisplay("Williams %R Period", "Period for Williams %R indicator", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 60) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetRange(0.5, 5.0) \
.SetDisplay("Stop-Loss %", "Stop-loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", tf(30)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._previous_williams_r = 0.0
self._cooldown = 0
self._vwap_date = None
self._vwap_cum_pv = 0.0
self._vwap_cum_vol = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(vwap_williams_r_strategy, self).OnReseted()
self._previous_williams_r = 0.0
self._cooldown = 0
self._vwap_date = None
self._vwap_cum_pv = 0.0
self._vwap_cum_vol = 0.0
def OnStarted2(self, time):
super(vwap_williams_r_strategy, self).OnStarted2(time)
self._previous_williams_r = 0.0
self._cooldown = 0
self._vwap_date = None
self._vwap_cum_pv = 0.0
self._vwap_cum_vol = 0.0
williams_r = WilliamsR()
williams_r.Length = self._williams_r_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(williams_r, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, williams_r)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, williams_r_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
date = candle.ServerTime.Date
if self._vwap_date is None or self._vwap_date != date:
self._vwap_date = date
self._vwap_cum_pv = 0.0
self._vwap_cum_vol = 0.0
self._vwap_cum_pv += float(candle.ClosePrice) * float(candle.TotalVolume)
self._vwap_cum_vol += float(candle.TotalVolume)
if self._vwap_cum_vol <= 0:
return
vwap_value = self._vwap_cum_pv / self._vwap_cum_vol
previous_wr = self._previous_williams_r
self._previous_williams_r = float(williams_r_value)
wr = float(williams_r_value)
price = float(candle.ClosePrice)
crossed_into_oversold = previous_wr > -80 and wr <= -80
crossed_into_overbought = previous_wr < -20 and wr >= -20
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldown_bars.Value)
if self._cooldown == 0 and price < vwap_value * 0.999 and crossed_into_oversold and self.Position <= 0:
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._cooldown = cooldown_val
elif self._cooldown == 0 and price > vwap_value * 1.001 and crossed_into_overbought and self.Position >= 0:
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._cooldown = cooldown_val
def CreateClone(self):
return vwap_williams_r_strategy()