Ver en GitHub

Estrategia de Reversión a la Media de Volumen

Este sistema busca volumen de negociación inusualmente alto o bajo en relación con su promedio histórico. Los picos de volumen significativos a menudo revierten a medida que la actividad se normaliza, ofreciendo posibles operaciones en contra del movimiento.

Las pruebas indican un rendimiento anual promedio de aproximadamente 76%. Funciona mejor en el mercado forex.

Se realiza una entrada larga cuando el volumen cae por debajo de la media menos DeviationMultiplier veces la desviación estándar y el precio está por debajo de la media móvil. Una entrada corta ocurre cuando el volumen sube por encima de la banda superior con el precio por encima de la media. Las operaciones se cierran una vez que el volumen regresa hacia su nivel medio.

La estrategia beneficia a los traders que observan el agotamiento después de los picos de volumen. Un stop-loss porcentual protege contra escenarios donde el volumen sigue expandiéndose en la misma dirección.

Detalles

  • Criterios de entrada:
    • Largo: Volume < Avg - DeviationMultiplier * StdDev && Close < MA
    • Corto: Volume > Avg + DeviationMultiplier * StdDev && Close > MA
  • Largo/Corto: Ambos lados.
  • Criterios de salida:
    • Largo: Salir cuando volume > Avg
    • Corto: Salir cuando volume < Avg
  • Stops: Sí, stop-loss porcentual.
  • Valores predeterminados:
    • AveragePeriod = 20
    • DeviationMultiplier = 2
    • CandleType = TimeSpan.FromMinutes(5)
    • StopLossPercent = 2 Avg y StdDev son la media y la desviación estándar de los últimos AveragePeriod valores de volumen, incluido el actual, y MA es la media móvil simple de los últimos AveragePeriod cierres. 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: Reversión a la media
    • Dirección: Ambos
    • Indicadores: Volume
    • 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>
/// Volume Mean Reversion strategy.
/// The bands lie DeviationMultiplier standard deviations around the average of the last AveragePeriod volume values, the current one included.
/// volume below the lower band with the close below the AveragePeriod simple moving average goes long and volume above the upper band with the close above it goes short,
/// reversing an opposite position. A long closes once volume is back above its average and a short once it is back below it, and a percent stop limits the loss.
/// </summary>
public class VolumeMeanReversionStrategy : Strategy
{
	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>
	/// Values of volume 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 VolumeMeanReversionStrategy()
	{
		_averagePeriod = Param(nameof(AveragePeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Average Period", "Values of volume 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 sma = new SimpleMovingAverage { Length = AveragePeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(sma, 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);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	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 smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var value = candle.TotalVolume;

		_values.Enqueue(value);

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

		if (_values.Count < AveragePeriod || !smaValue.IsFormed)
			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;
		var close = candle.ClosePrice;
		var ma = smaValue.GetValue<decimal>();

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