Ver en GitHub

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();
		}
	}
}