Vwap Volume Strategy
Strategy combining VWAP and Volume indicators. Buys/sells on VWAP breakouts confirmed by above-average volume.
Testing indicates an average annual return of about 52%. It performs best in the crypto market.
This strategy references VWAP to gauge value and requires volume confirmation before trades. The idea is to join moves backed by strong participation.
Intraday traders focused on volume metrics can employ this method. Losses are trimmed via an ATR-based stop.
Details
- Entry Criteria:
- Long:
Close < VWAP && Volume > AvgVolume * VolumeThreshold - Short:
Close > VWAP && Volume > AvgVolume * VolumeThreshold
- Long:
- Long/Short: Both
- Exit Criteria:
- Price crosses back through VWAP
- Stops: Percent-based using
StopLossPercent - Default Values:
VolumePeriod= 20VolumeThreshold= 1.5mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: VWAP, Volume
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy combining VWAP with volume confirmation.
/// Buys on VWAP breakout with above-average volume, sells on breakdown.
/// </summary>
public class VwapVolumeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<decimal> _volumeThreshold;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _volumes = new();
private readonly List<decimal> _typicalPriceVol = new();
private decimal _cumVol;
private decimal _cumTpv;
private int _cooldown;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Period for volume moving average.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// Volume threshold multiplier.
/// </summary>
public decimal VolumeThreshold
{
get => _volumeThreshold.Value;
set => _volumeThreshold.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public VwapVolumeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetRange(10, 50)
.SetDisplay("Volume MA Period", "Period for volume moving average", "Indicators");
_volumeThreshold = Param(nameof(VolumeThreshold), 1.5m)
.SetDisplay("Volume Threshold", "Multiplier for average volume", "Trading Levels");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumes.Clear();
_typicalPriceVol.Clear();
_cumVol = 0;
_cumTpv = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = VolumePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var vol = candle.TotalVolume;
var typicalPrice = (high + low + close) / 3m;
_volumes.Add(vol);
_cumVol += vol;
_cumTpv += typicalPrice * vol;
var volPrd = VolumePeriod;
if (_volumes.Count < volPrd)
{
if (_cooldown > 0) _cooldown--;
return;
}
// Manual VWAP (cumulative)
var vwapValue = _cumVol > 0 ? _cumTpv / _cumVol : close;
// Manual volume average
decimal sumVol = 0;
var count = _volumes.Count;
for (int i = count - volPrd; i < count; i++)
sumVol += _volumes[i];
var avgVol = sumVol / volPrd;
var highVolume = vol > avgVol * VolumeThreshold;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Buy: price above VWAP + high volume
if (close > vwapValue && highVolume && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Sell: price below VWAP + high volume
else if (close < vwapValue && highVolume && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price below VWAP
if (Position > 0 && close < vwapValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price above VWAP
else if (Position < 0 && close > vwapValue)
{
BuyMarket();
_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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class vwap_volume_strategy(Strategy):
"""
Strategy combining VWAP with volume confirmation.
"""
def __init__(self):
super(vwap_volume_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_period = self.Param("VolumePeriod", 20) \
.SetRange(10, 50) \
.SetDisplay("Volume MA Period", "Period for volume moving average", "Indicators")
self._volume_threshold = self.Param("VolumeThreshold", 1.5) \
.SetDisplay("Volume Threshold", "Multiplier for average volume", "Trading Levels")
self._cooldown_bars = self.Param("CooldownBars", 100) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General") \
.SetRange(5, 500)
self._volumes = []
self._cum_vol = 0.0
self._cum_tpv = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(vwap_volume_strategy, self).OnStarted2(time)
self._volumes = []
self._cum_vol = 0.0
self._cum_tpv = 0.0
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self._volume_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
vol = float(candle.TotalVolume)
typical_price = (high + low + close) / 3.0
self._volumes.append(vol)
self._cum_vol += vol
self._cum_tpv += typical_price * vol
vol_prd = self._volume_period.Value
if len(self._volumes) < vol_prd:
if self._cooldown > 0:
self._cooldown -= 1
return
# Manual VWAP (cumulative)
vwap_value = self._cum_tpv / self._cum_vol if self._cum_vol > 0 else close
# Manual volume average
count = len(self._volumes)
sum_vol = sum(self._volumes[count - vol_prd:count])
avg_vol = sum_vol / vol_prd
high_volume = vol > avg_vol * self._volume_threshold.Value
if self._cooldown > 0:
self._cooldown -= 1
return
cd = self._cooldown_bars.Value
# Buy: price above VWAP + high volume
if close > vwap_value and high_volume and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
elif close < vwap_value and high_volume and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit long: price below VWAP
if self.Position > 0 and close < vwap_value:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > vwap_value:
self.BuyMarket()
self._cooldown = cd
def OnReseted(self):
super(vwap_volume_strategy, self).OnReseted()
self._volumes = []
self._cum_vol = 0.0
self._cum_tpv = 0.0
self._cooldown = 0
def CreateClone(self):
return vwap_volume_strategy()