Auf GitHub ansehen

VWAP Bounce-Strategie

Der volumengewichtete Durchschnittspreis (VWAP) ist ein beliebter Intraday-Referenzwert. Wenn der Kurs erheblich vom VWAP abweicht und dann eine Kerze zurück in Richtung VWAP druckt, folgt oft eine kurze Umkehrbewegung. Diese Strategie handelt diese Abpraller.

Tests zeigen eine durchschnittliche Jahresrendite von etwa 130%. Die Strategie eignet sich am besten für den Aktienmarkt.

Für jeden Balken wird der aktuelle VWAP berechnet. Wenn eine bullische Kerze unterhalb des VWAP schließt, geht das System Long; wenn eine bearische Kerze oberhalb des VWAP schließt, geht es Short. Ein fester Stop-Loss-Prozentsatz steuert das Risiko, und Positionen werden in der Regel nur gehalten, bis ein entgegengesetztes Signal entsteht oder der Stop erreicht wird.

Da die Methode gegen Intraday-Extreme handelt, funktioniert sie am besten in Range-gebundenen Märkten statt in starken Trends.

Details

  • Einstiegskriterien: Schlusskurs unterhalb VWAP mit bullischer Kerze oder oberhalb VWAP mit bearischer Kerze.
  • Long/Short: Beide.
  • Ausstiegskriterien: Entgegengesetztes Signal oder Stop-Loss.
  • Stops: Ja, prozentbasiert.
  • Standardwerte:
    • CandleType = 5 minute
    • StopLoss = 2%
  • Filter:
    • Kategorie: Mean Reversion
    • Richtung: Beide
    • Indikatoren: VWAP
    • Stops: Ja
    • Komplexität: Grundlegend
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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>
/// VWAP Bounce strategy.
/// Enters long when price bounces off VWAP from below with a bullish candle.
/// Enters short when price bounces off VWAP from above with a bearish candle.
/// Uses SMA for exit signals.
/// </summary>
public class VwapBounceStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private decimal _prevClose;
	private int _cooldown;

	/// <summary>
	/// MA Period.
	/// </summary>
	public int MAPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Cooldown bars.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public VwapBounceStrategy()
	{
		_maPeriod = Param(nameof(MAPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Period for SMA", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_cooldownBars = Param(nameof(CooldownBars), 500)
			.SetRange(1, 1000)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevClose = default;
		_cooldown = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevClose = 0;
		_cooldown = 0;

		var vwma = new VolumeWeightedMovingAverage { Length = 20 };
		var sma = new SimpleMovingAverage { Length = MAPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(vwma, sma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, vwma);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal vwmaValue, decimal smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_prevClose == 0)
		{
			_prevClose = candle.ClosePrice;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevClose = candle.ClosePrice;
			return;
		}

		var isBullish = candle.ClosePrice > candle.OpenPrice;
		var isBearish = candle.ClosePrice < candle.OpenPrice;

		// Bounce off VWAP from below (bullish): prev close was below VWAP, now above or near, bullish candle
		var bouncedUp = _prevClose < vwmaValue && candle.ClosePrice >= vwmaValue && isBullish;
		// Bounce off VWAP from above (bearish): prev close was above VWAP, now below or near, bearish candle
		var bouncedDown = _prevClose > vwmaValue && candle.ClosePrice <= vwmaValue && isBearish;

		if (Position == 0 && bouncedUp)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
		else if (Position == 0 && bouncedDown)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position > 0 && candle.ClosePrice < smaValue)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && candle.ClosePrice > smaValue)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}

		_prevClose = candle.ClosePrice;
	}
}