Ver en GitHub

Estrategia de Reversión a la Media con Williams %R

Williams %R oscila entre 0 y -100 para mostrar cuándo el precio cierra cerca de los extremos de su rango reciente. Esta estrategia opera contra esos extremos una vez que el indicador se estira lejos de su propio promedio.

Las pruebas indican un retorno anual promedio de aproximadamente 154%. Funciona mejor en el mercado de acciones.

Una operación larga se activa cuando Williams %R cae por debajo del promedio menos DeviationMultiplier veces la desviación estándar. Se toma una operación corta cuando sube por encima del promedio más ese multiplicador. Las salidas ocurren cuando Williams %R vuelve hacia su nivel promedio.

El enfoque es adecuado para traders que dependen del agotamiento del impulso para programar las entradas. Un stop-loss de protección limita el riesgo si el precio sigue moviéndose hacia nuevos extremos.

Detalles

  • Criterios de entrada:
    • Largo: %R < Avg - DeviationMultiplier * StdDev
    • Corto: %R > Avg + DeviationMultiplier * StdDev
  • Largo/Corto: Ambos lados.
  • Criterios de salida:
    • Largo: Salir cuando %R > Avg
    • Corto: Salir cuando %R < Avg
  • Stops: Sí, stop-loss porcentual.
  • Valores predeterminados:
    • WilliamsRPeriod = 14
    • AveragePeriod = 20
    • DeviationMultiplier = 2
    • StopLossPercent = 2
    • CandleType = TimeSpan.FromMinutes(5) Avg y StdDev son la media y la desviación estándar de los últimos AveragePeriod valores de %R, incluido el actual. El stop es un StopLossPercent fijo del precio de entrada, vigilado también entre velas; 0 lo desactiva. Una señal de entrada contra una posición abierta la invierte.
  • Filtros:
    • Categoría: Mean reversion
    • Dirección: Ambos
    • Indicadores: Williams %R
    • Stops: Sí
    • Complejidad: Intermedio
    • Marco temporal: Intradía
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using System.Linq;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Williams R Mean Reversion strategy.
/// The bands lie DeviationMultiplier standard deviations around the average of the last AveragePeriod %R values, the current one included.
/// %R below the lower band goes long and %R above the upper band goes short,
/// reversing an opposite position. A long closes once %R is back above its average and a short once it is back below it, and a percent stop limits the loss.
/// </summary>
public class WilliamsRMeanReversionStrategy : Strategy
{
	private readonly StrategyParam<int> _williamsRPeriod;
	private readonly StrategyParam<int> _averagePeriod;
	private readonly StrategyParam<decimal> _deviationMultiplier;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _values = [];

	/// <summary>
	/// Period of Williams %R.
	/// </summary>
	public int WilliamsRPeriod
	{
		get => _williamsRPeriod.Value;
		set => _williamsRPeriod.Value = value;
	}

	/// <summary>
	/// Values of %R the average and the standard deviation span.
	/// </summary>
	public int AveragePeriod
	{
		get => _averagePeriod.Value;
		set => _averagePeriod.Value = value;
	}

	/// <summary>
	/// Standard deviations between the average and a band.
	/// </summary>
	public decimal DeviationMultiplier
	{
		get => _deviationMultiplier.Value;
		set => _deviationMultiplier.Value = value;
	}

	/// <summary>
	/// Stop loss percentage from entry price.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public WilliamsRMeanReversionStrategy()
	{
		_williamsRPeriod = Param(nameof(WilliamsRPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Williams %R Period", "Period of Williams %R", "Indicators");

		_averagePeriod = Param(nameof(AveragePeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Average Period", "Values of %R the average and the standard deviation span", "Indicators");

		_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Multiplier", "Standard deviations between the average and a band", "Indicators");

		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetNotNegative()
			.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
		_values.Clear();
	}

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

		_values.Clear();

		var williams = new WilliamsR { Length = WilliamsRPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(williams, ProcessCandle)
			.Start();

		StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);

		// The stop has to see prices between candles, not only at their close.
		foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
		{
			var quotes = new Subscription(DataType.Level1, Security);
			quotes.MarketData.BuildField = field;
			SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
		}

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);

			var oscillators = CreateChartArea();
			if (oscillators != null)
			{
				DrawIndicator(oscillators, williams);
			}
		}
	}

	private void ObserveProtectionQuote(Level1ChangeMessage quote)
	{
		// The high-level handler activates native protection before this callback, also between signal bars.
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue williamsValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!williamsValue.IsFormed)
			return;

		var value = williamsValue.GetValue<decimal>();

		_values.Enqueue(value);

		if (_values.Count > AveragePeriod)
			_values.Dequeue();

		if (_values.Count < AveragePeriod)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var mean = _values.Average();
		var deviation = (decimal)Math.Sqrt((double)_values.Average(v => (v - mean) * (v - mean)));
		var upper = mean + DeviationMultiplier * deviation;
		var lower = mean - DeviationMultiplier * deviation;

		if (value < lower && Position <= 0)
			BuyMarket(Volume + Math.Abs(Position));
		else if (value > upper && Position >= 0)
			SellMarket(Volume + Math.Abs(Position));
		else if (Position > 0 && value > mean)
			SellMarket(Position);
		else if (Position < 0 && value < mean)
			BuyMarket(-Position);
	}
}