Ver en GitHub

Estrategia de Multiplicador de Volumen ADX

La Estrategia de Multiplicador de Volumen ADX combina la fortaleza de tendencia del Average Directional Index con un filtro de aumento de volumen. Entra en operaciones solo cuando el ADX supera un umbral, la línea direccional dominante apunta hacia la dirección de la tendencia y el volumen actual supera una media móvil multiplicada por un factor definido por el usuario.

Detalles

  • Criterios de entrada:
    • ADX por encima del umbral y DI+ > DI- con volumen mayor que SMA(volumen) * multiplicador → largo.
    • ADX por encima del umbral y DI- > DI+ con volumen mayor que SMA(volumen) * multiplicador → corto.
  • Largo/Corto: Ambos.
  • Criterios de salida:
    • Una señal inversa activa la reversión de posición.
  • Stops: Ninguno.
  • Valores predeterminados:
    • AdxPeriod = 21
    • AdxThreshold = 26
    • VolumeMultiplier = 1.8
    • VolumePeriod = 20
  • Filtros:
    • Categoría: Seguimiento de tendencia
    • Dirección: Ambos
    • Indicadores: ADX, Volume SMA
    • Stops: No
    • Complejidad: Bajo
    • Marco temporal: Cualquiera
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
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>
/// ADX Volume Multiplier strategy.
/// When ADX is above AdxThreshold and the candle volume exceeds VolumeMultiplier times its SMA, the strategy goes long if DI+ is above
/// DI- and short if DI- is above DI+; the opposite signal reverses the position.
/// </summary>
public class AdxVolumeMultiplierStrategy : Strategy
{
	private readonly StrategyParam<int> _adxPeriod;
	private readonly StrategyParam<decimal> _adxThreshold;
	private readonly StrategyParam<decimal> _volumeMultiplier;
	private readonly StrategyParam<int> _volumePeriod;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _volumeSma;

	/// <summary>
	/// ADX period.
	/// </summary>
	public int AdxPeriod
	{
		get => _adxPeriod.Value;
		set => _adxPeriod.Value = value;
	}

	/// <summary>
	/// ADX level a trend must exceed.
	/// </summary>
	public decimal AdxThreshold
	{
		get => _adxThreshold.Value;
		set => _adxThreshold.Value = value;
	}

	/// <summary>
	/// Multiple of the average volume the candle volume must exceed.
	/// </summary>
	public decimal VolumeMultiplier
	{
		get => _volumeMultiplier.Value;
		set => _volumeMultiplier.Value = value;
	}

	/// <summary>
	/// Period of the volume SMA.
	/// </summary>
	public int VolumePeriod
	{
		get => _volumePeriod.Value;
		set => _volumePeriod.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public AdxVolumeMultiplierStrategy()
	{
		_adxPeriod = Param(nameof(AdxPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("ADX Period", "ADX period", "ADX");

		_adxThreshold = Param(nameof(AdxThreshold), 26m)
			.SetDisplay("ADX Threshold", "ADX level a trend must exceed", "ADX");

		_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.8m)
			.SetGreaterThanZero()
			.SetDisplay("Volume Multiplier", "Multiple of the average volume the candle volume must exceed", "Volume");

		_volumePeriod = Param(nameof(VolumePeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Volume Period", "Period of the volume SMA", "Volume");

		_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();
		_volumeSma = null;
	}

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

		var adx = new AverageDirectionalIndex { Length = AdxPeriod };
		_volumeSma = new SimpleMovingAverage { Length = VolumePeriod };

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

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

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

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

		var volumeAverage = _volumeSma.Process(candle.TotalVolume, candle.ServerTime, true).ToDecimal();

		if (!adxValue.IsFormed || !_volumeSma.IsFormed)
			return;

		var adxTyped = (IAverageDirectionalIndexValue)adxValue;
		if (adxTyped.MovingAverage is not decimal adx || adxTyped.Dx.Plus is not decimal diPlus || adxTyped.Dx.Minus is not decimal diMinus)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var volumeSurge = candle.TotalVolume > volumeAverage * VolumeMultiplier;
		var strong = adx > AdxThreshold;

		if (strong && volumeSurge && diPlus > diMinus && Position <= 0)
			BuyMarket(Volume + Math.Abs(Position));
		else if (strong && volumeSurge && diMinus > diPlus && Position >= 0)
			SellMarket(Volume + Math.Abs(Position));
	}
}