Auf GitHub ansehen

Strategie VWAP Williams R

Die VWAP Williams %R-Strategie konzentriert sich auf die Intraday-Reversion rund um den volumengewichteten Durchschnittspreis. Sie beobachtet, wenn der Preis vom VWAP abdriftet, während der Williams %R-Oszillator überverkaufte oder überkaufte Bereiche erreicht. Die Annahme ist, dass extreme Werte nahe dem VWAP oft zu einem Rückschlag zum Mittelwert führen.

Tests zeigen eine durchschnittliche Jahresrendite von etwa 40%. Die Strategie funktioniert am besten auf dem Kryptomarkt.

Wenn der Oszillator unter -80 fällt und der Preis unter dem VWAP handelt, impliziert das Setup, dass der Verkaufsdruck nachlässt und eine Erholung folgen kann. Umgekehrt warnt ein Wert über -20, während der Preis über dem VWAP liegt, dass Käufer erschöpft sind und ein Rückgang wahrscheinlich ist. Die Strategie eröffnet Trades in Richtung einer potenziellen Rückkehr zum VWAP und wartet darauf, dass diese Bewegung abgeschlossen wird.

Dieser Ansatz eignet sich für aktive Intraday-Trader, die häufige Mean-Reversion-Möglichkeiten bevorzugen. Ein kleiner Stop‑Loss relativ zum VWAP hält das Risiko begrenzt und lässt gleichzeitig genug Raum für Preisschwankungen vor der Umkehr.

Details

  • Einstiegskriterien:
    • Long: Price < VWAP && Williams %R < -80 (überverkauft unterhalb VWAP)
    • Short: Price > VWAP && Williams %R > -20 (überkauft oberhalb VWAP)
  • Long/Short: Beide Seiten.
  • Ausstiegskriterien:
    • Long: Long-Position schließen, wenn der Preis über VWAP steigt
    • Short: Short-Position schließen, wenn der Preis unter VWAP fällt
  • Stops: Ja.
  • Standardwerte:
    • WilliamsRPeriod = 14
    • StopLossPercent = 2m
    • CandleType = TimeSpan.FromMinutes(5)
  • Filter:
    • Kategorie: Gemischt
    • Richtung: Beide
    • Indikatoren: VWAP Williams R
    • Stops: Ja
    • Komplexität: Mittel
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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;
		}
	}
}