VWAP Strategy
Uses VWAP with entry bands and multiple exit modes. Buys when price closes above the lower band and sells when price closes below the upper band. Supports VWAP or deviation band exits and optional safety exit after consecutive opposing bars.
Parameters
- StopPoints: Stop buffer from signal bar.
- ExitModeLong: Exit mode for long positions.
- ExitModeShort: Exit mode for short positions.
- TargetLongDeviation: Deviation multiplier for long target.
- TargetShortDeviation: Deviation multiplier for short target.
- EnableSafetyExit: Enable safety exit after opposing bars.
- NumOpposingBars: Number of opposing bars for safety exit.
- AllowLongs: Allow long trades.
- AllowShorts: Allow short trades.
- MinStrength: Minimum signal strength.
- CandleType: Type of candles.
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>
/// VWAP strategy with entry bands, signal-based stops and multiple exit modes.
/// </summary>
public class VwapStrategy : Strategy
{
public enum ExitModes
{
Vwap,
Deviation,
None
}
private readonly StrategyParam<decimal> _stopPoints;
private readonly StrategyParam<ExitModes> _exitModeLong;
private readonly StrategyParam<ExitModes> _exitModeShort;
private readonly StrategyParam<decimal> _targetLongDeviation;
private readonly StrategyParam<decimal> _targetShortDeviation;
private readonly StrategyParam<bool> _enableSafetyExit;
private readonly StrategyParam<int> _numOpposingBars;
private readonly StrategyParam<bool> _allowLongs;
private readonly StrategyParam<bool> _allowShorts;
private readonly StrategyParam<decimal> _minStrength;
private readonly StrategyParam<DataType> _candleType;
private DateTime _sessionDate;
private decimal _sumSrc;
private decimal _sumVol;
private decimal _sumSrcSqVol;
private decimal? _signalLow;
private decimal? _signalHigh;
private int _bullCount;
private int _bearCount;
/// <summary>
/// Stop buffer in price points.
/// </summary>
public decimal StopPoints { get => _stopPoints.Value; set => _stopPoints.Value = value; }
/// <summary>
/// Long exit mode.
/// </summary>
public ExitModes ExitModeLong { get => _exitModeLong.Value; set => _exitModeLong.Value = value; }
/// <summary>
/// Short exit mode.
/// </summary>
public ExitModes ExitModeShort { get => _exitModeShort.Value; set => _exitModeShort.Value = value; }
/// <summary>
/// Deviation multiplier for long targets.
/// </summary>
public decimal TargetLongDeviation { get => _targetLongDeviation.Value; set => _targetLongDeviation.Value = value; }
/// <summary>
/// Deviation multiplier for short targets.
/// </summary>
public decimal TargetShortDeviation { get => _targetShortDeviation.Value; set => _targetShortDeviation.Value = value; }
/// <summary>
/// Enable safety exit.
/// </summary>
public bool EnableSafetyExit { get => _enableSafetyExit.Value; set => _enableSafetyExit.Value = value; }
/// <summary>
/// Number of opposing bars for safety exit.
/// </summary>
public int NumOpposingBars { get => _numOpposingBars.Value; set => _numOpposingBars.Value = value; }
/// <summary>
/// Allow long trades.
/// </summary>
public bool AllowLongs { get => _allowLongs.Value; set => _allowLongs.Value = value; }
/// <summary>
/// Allow short trades.
/// </summary>
public bool AllowShorts { get => _allowShorts.Value; set => _allowShorts.Value = value; }
/// <summary>
/// Minimum signal strength.
/// </summary>
public decimal MinStrength { get => _minStrength.Value; set => _minStrength.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VwapStrategy()
{
_stopPoints = Param(nameof(StopPoints), 20m)
.SetGreaterThanZero()
.SetDisplay("Stop Points", "Stop buffer from signal bar", "Parameters");
_exitModeLong = Param(nameof(ExitModeLong), ExitModes.Vwap)
.SetDisplay("Long Exit Mode", string.Empty, "Parameters");
_exitModeShort = Param(nameof(ExitModeShort), ExitModes.Vwap)
.SetDisplay("Short Exit Mode", string.Empty, "Parameters");
_targetLongDeviation = Param(nameof(TargetLongDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Long Target Deviation", string.Empty, "Parameters");
_targetShortDeviation = Param(nameof(TargetShortDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Short Target Deviation", string.Empty, "Parameters");
_enableSafetyExit = Param(nameof(EnableSafetyExit), true)
.SetDisplay("Enable Safety Exit", string.Empty, "Parameters");
_numOpposingBars = Param(nameof(NumOpposingBars), 3)
.SetGreaterThanZero()
.SetDisplay("Opposing Bars", string.Empty, "Parameters");
_allowLongs = Param(nameof(AllowLongs), true)
.SetDisplay("Allow Longs", string.Empty, "Parameters");
_allowShorts = Param(nameof(AllowShorts), true)
.SetDisplay("Allow Shorts", string.Empty, "Parameters");
_minStrength = Param(nameof(MinStrength), 0.7m)
.SetGreaterThanZero()
.SetDisplay("Min Strength", string.Empty, "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sessionDate = default;
_sumSrc = 0m;
_sumVol = 0m;
_sumSrcSqVol = 0m;
_signalLow = null;
_signalHigh = null;
_bullCount = 0;
_bearCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal _dummy)
{
if (candle.State != CandleStates.Finished)
return;
var date = candle.OpenTime.Date;
var vol = candle.TotalVolume;
var src = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
if (date != _sessionDate)
{
_sessionDate = date;
_sumSrc = src * vol;
_sumVol = vol;
_sumSrcSqVol = src * src * vol;
}
else
{
_sumSrc += src * vol;
_sumVol += vol;
_sumSrcSqVol += src * src * vol;
}
if (_sumVol == 0m)
return;
var vwap = _sumSrc / _sumVol;
var variance = _sumSrcSqVol / _sumVol - vwap * vwap;
var stdev = (decimal)Math.Sqrt((double)Math.Max(variance, 0m));
var entryUpper = vwap + stdev * 2m;
var entryLower = vwap - stdev * 2m;
var targetUpperLong = vwap + stdev * TargetLongDeviation;
var targetLowerShort = vwap - stdev * TargetShortDeviation;
var barRange = candle.HighPrice - candle.LowPrice;
var bullStrength = barRange > 0m ? (candle.ClosePrice - candle.LowPrice) / barRange : 0m;
var bearStrength = barRange > 0m ? (candle.HighPrice - candle.ClosePrice) / barRange : 0m;
if (candle.ClosePrice > candle.OpenPrice)
{
_bullCount++;
_bearCount = 0;
}
else if (candle.ClosePrice < candle.OpenPrice)
{
_bearCount++;
_bullCount = 0;
}
else
{
_bullCount = 0;
_bearCount = 0;
}
var longCondition = AllowLongs && candle.OpenPrice < entryLower && candle.ClosePrice > entryLower && bullStrength >= MinStrength && Position == 0;
var shortCondition = AllowShorts && candle.OpenPrice > entryUpper && candle.ClosePrice < entryUpper && bearStrength >= MinStrength && Position == 0;
if (longCondition)
{
BuyMarket();
_signalLow = candle.LowPrice;
_signalHigh = null;
}
else if (shortCondition)
{
SellMarket();
_signalHigh = candle.HighPrice;
_signalLow = null;
}
if (Position == 0)
{
_signalLow = null;
_signalHigh = null;
}
if (Position > 0 && _signalLow.HasValue)
{
var stop = _signalLow.Value - StopPoints;
var exitVwap = ExitModeLong == ExitModes.Vwap && candle.HighPrice >= vwap;
var exitDev = ExitModeLong == ExitModes.Deviation && candle.HighPrice >= targetUpperLong;
if (candle.LowPrice <= stop || (ExitModeLong != ExitModes.None && (exitVwap || exitDev)))
SellMarket();
else if (EnableSafetyExit && _bearCount >= NumOpposingBars)
SellMarket();
}
else if (Position < 0 && _signalHigh.HasValue)
{
var stop = _signalHigh.Value + StopPoints;
var exitVwap = ExitModeShort == ExitModes.Vwap && candle.LowPrice <= vwap;
var exitDev = ExitModeShort == ExitModes.Deviation && candle.LowPrice <= targetLowerShort;
if (candle.HighPrice >= stop || (ExitModeShort != ExitModes.None && (exitVwap || exitDev)))
BuyMarket();
else if (EnableSafetyExit && _bullCount >= NumOpposingBars)
BuyMarket();
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vwap_strategy(Strategy):
EXIT_VWAP = 0
EXIT_DEVIATION = 1
EXIT_NONE = 2
def __init__(self):
super(vwap_strategy, self).__init__()
self._stop_points = self.Param("StopPoints", 20.0) \
.SetDisplay("Stop Points", "Stop buffer from signal bar", "Parameters")
self._exit_mode_long = self.Param("ExitModeLong", 0) \
.SetDisplay("Long Exit Mode", "", "Parameters")
self._exit_mode_short = self.Param("ExitModeShort", 0) \
.SetDisplay("Short Exit Mode", "", "Parameters")
self._target_long_deviation = self.Param("TargetLongDeviation", 2.0) \
.SetDisplay("Long Target Deviation", "", "Parameters")
self._target_short_deviation = self.Param("TargetShortDeviation", 2.0) \
.SetDisplay("Short Target Deviation", "", "Parameters")
self._enable_safety_exit = self.Param("EnableSafetyExit", True) \
.SetDisplay("Enable Safety Exit", "", "Parameters")
self._num_opposing_bars = self.Param("NumOpposingBars", 3) \
.SetDisplay("Opposing Bars", "", "Parameters")
self._allow_longs = self.Param("AllowLongs", True) \
.SetDisplay("Allow Longs", "", "Parameters")
self._allow_shorts = self.Param("AllowShorts", True) \
.SetDisplay("Allow Shorts", "", "Parameters")
self._min_strength = self.Param("MinStrength", 0.7) \
.SetDisplay("Min Strength", "", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "Parameters")
self._session_date = None
self._sum_src = 0.0
self._sum_vol = 0.0
self._sum_src_sq_vol = 0.0
self._signal_low = None
self._signal_high = None
self._bull_count = 0
self._bear_count = 0
@property
def stop_points(self):
return self._stop_points.Value
@property
def exit_mode_long(self):
return self._exit_mode_long.Value
@property
def exit_mode_short(self):
return self._exit_mode_short.Value
@property
def target_long_deviation(self):
return self._target_long_deviation.Value
@property
def target_short_deviation(self):
return self._target_short_deviation.Value
@property
def enable_safety_exit(self):
return self._enable_safety_exit.Value
@property
def num_opposing_bars(self):
return self._num_opposing_bars.Value
@property
def allow_longs(self):
return self._allow_longs.Value
@property
def allow_shorts(self):
return self._allow_shorts.Value
@property
def min_strength(self):
return self._min_strength.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vwap_strategy, self).OnReseted()
self._session_date = None
self._sum_src = 0.0
self._sum_vol = 0.0
self._sum_src_sq_vol = 0.0
self._signal_low = None
self._signal_high = None
self._bull_count = 0
self._bear_count = 0
def OnStarted2(self, time):
super(vwap_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = 2
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, dummy):
if candle.State != CandleStates.Finished:
return
date = candle.OpenTime.Date
vol = float(candle.TotalVolume)
src = (float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 3.0
if self._session_date is None or date != self._session_date:
self._session_date = date
self._sum_src = src * vol
self._sum_vol = vol
self._sum_src_sq_vol = src * src * vol
else:
self._sum_src += src * vol
self._sum_vol += vol
self._sum_src_sq_vol += src * src * vol
if self._sum_vol == 0:
return
vwap = self._sum_src / self._sum_vol
variance = self._sum_src_sq_vol / self._sum_vol - vwap * vwap
stdev = Math.Sqrt(float(max(variance, 0)))
entry_upper = vwap + stdev * 2.0
entry_lower = vwap - stdev * 2.0
target_upper_long = vwap + stdev * self.target_long_deviation
target_lower_short = vwap - stdev * self.target_short_deviation
bar_range = float(candle.HighPrice) - float(candle.LowPrice)
bull_strength = (float(candle.ClosePrice) - float(candle.LowPrice)) / bar_range if bar_range > 0 else 0.0
bear_strength = (float(candle.HighPrice) - float(candle.ClosePrice)) / bar_range if bar_range > 0 else 0.0
if candle.ClosePrice > candle.OpenPrice:
self._bull_count += 1
self._bear_count = 0
elif candle.ClosePrice < candle.OpenPrice:
self._bear_count += 1
self._bull_count = 0
else:
self._bull_count = 0
self._bear_count = 0
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
long_condition = self.allow_longs and opn < entry_lower and close > entry_lower and bull_strength >= self.min_strength and self.Position == 0
short_condition = self.allow_shorts and opn > entry_upper and close < entry_upper and bear_strength >= self.min_strength and self.Position == 0
if long_condition:
self.BuyMarket()
self._signal_low = low
self._signal_high = None
elif short_condition:
self.SellMarket()
self._signal_high = high
self._signal_low = None
if self.Position == 0:
self._signal_low = None
self._signal_high = None
if self.Position > 0 and self._signal_low is not None:
stop = self._signal_low - self.stop_points
exit_vwap = self.exit_mode_long == self.EXIT_VWAP and high >= vwap
exit_dev = self.exit_mode_long == self.EXIT_DEVIATION and high >= target_upper_long
if low <= stop or (self.exit_mode_long != self.EXIT_NONE and (exit_vwap or exit_dev)):
self.SellMarket()
elif self.enable_safety_exit and self._bear_count >= self.num_opposing_bars:
self.SellMarket()
elif self.Position < 0 and self._signal_high is not None:
stop = self._signal_high + self.stop_points
exit_vwap = self.exit_mode_short == self.EXIT_VWAP and low <= vwap
exit_dev = self.exit_mode_short == self.EXIT_DEVIATION and low <= target_lower_short
if high >= stop or (self.exit_mode_short != self.EXIT_NONE and (exit_vwap or exit_dev)):
self.BuyMarket()
elif self.enable_safety_exit and self._bull_count >= self.num_opposing_bars:
self.BuyMarket()
def CreateClone(self):
return vwap_strategy()