Ver en GitHub

Estrategia de Volatilidad de Stochastic Chaikin's

Descripción general

Esta estrategia es un port en StockSharp del asesor experto MetaTrader Exp_Stochastic_Chaikins_Volatility. Analiza la diferencia entre los precios máximo y mínimo, suaviza esa volatilidad con una media móvil configurable y luego normaliza el resultado usando un oscilador similar al estocástico. Las decisiones de trading siguen la lógica contraria a la tendencia original: la estrategia busca puntos de giro en el oscilador para desvanecerse los extremos a corto plazo mientras cierra opcionalmente las posiciones existentes cuando el impulso se invierte.

Construcción del indicador

  1. Volatilidad estilo Chaikin – la diferencia entre el máximo y el mínimo de la vela se suaviza con la media móvil primaria. Los métodos de suavizado admitidos son:
    • Simple (SMA)
    • Exponencial (EMA)
    • Suavizada/Wilder (SMMA)
    • Ponderada linealmente (LWMA)
    • Jurik (aproximación JMA)
  2. Normalización estocástica – los Stochastic Length valores suavizados más recientes definen el rango más alto y más bajo. El valor suavizado actual se normaliza en un rango de 0–100 usando esa ventana.
  3. Suavizado secundario – una segunda media móvil (método seleccionable de la misma lista) se aplica al valor normalizado para obtener la línea principal del oscilador. Internamente la línea de señal es simplemente el valor del oscilador de la vela completada anterior, replicando el comportamiento del buffer del indicador MQL.

Lógica de trading

  • Entrada
    • Comprar: cuando el oscilador principal formó un máximo más bajo (el valor anterior es mayor que su propio valor previo, el valor actual cruza por debajo de ese valor anterior). Esto refleja el disparador largo contrario del EA original.
    • Vender: cuando el oscilador formó un mínimo más alto (el valor anterior es menor que su propio valor previo, el valor actual cruza por encima de ese valor anterior).
  • Salida
    • Las posiciones largas se cierran cuando el valor del oscilador anterior se mueve por debajo de su valor más antiguo (reaparece el impulso descendente).
    • Las posiciones cortas se cierran cuando el valor del oscilador anterior sube por encima de su valor más antiguo.
  • La evaluación de señales usa el parámetro Signal Shift para inspeccionar velas completadas. Los valores predeterminados emulan la configuración MQL de 1 barra.

Parámetros

Nombre Descripción
Candle Type Marco temporal usado para todos los cálculos (por defecto velas temporales de 4 horas).
Primary Method / Primary Length Tipo y longitud de media móvil para suavizar la diferencia máximo–mínimo.
Secondary Method / Secondary Length Tipo y longitud de media móvil para suavizar el oscilador normalizado.
Stochastic Length Ventana de retrospectiva para el rango más alto/más bajo usado en el paso de normalización.
Signal Shift Número de velas completadas entre la barra actual y la barra usada para la evaluación de señal. Debe mantenerse ≥1.
Allow Long/Short Entry Habilitar o deshabilitar la apertura de operaciones largas o cortas.
Allow Long/Short Exit Habilitar o deshabilitar el cierre de posición cuando el oscilador se invierte.
High/Middle/Low Level Niveles de guía visual reproducidos del indicador original (sin efecto directo en el trading).

Notas de uso

  • El port de StockSharp mantiene el comportamiento contrario a la tendencia original pero usa las medias móviles de StockSharp. Los métodos exóticos de la biblioteca MQL (ParMA, VIDYA, AMA, etc.) se mapean a la opción de suavizado disponible más cercana; elija Jurik para una aproximación más cercana cuando sea necesario.
  • El dimensionamiento de posición sigue la propiedad Volume de la estrategia base. La gestión de stop-loss y take-profit de la biblioteca auxiliar MQL no se replica; las salidas dependen de reversiones del oscilador o gestión de riesgo externa como StartProtection.
  • Las señales se calculan solo en velas terminadas. Asegúrese de que el feed de datos proporcione el Candle Type seleccionado con suficiente historial para que ambas etapas de suavizado y la ventana estocástica puedan calentarse.
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 converted from the "Stochastic Chaikin's Volatility" MQL expert advisor.
/// Combines a smoothed Chaikin volatility measure with a stochastic oscillator style normalization.
/// </summary>
public class StochasticChaikinsVolatilityStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<SmoothMethods> _primaryMethod;
	private readonly StrategyParam<int> _primaryLength;
	private readonly StrategyParam<SmoothMethods> _secondaryMethod;
	private readonly StrategyParam<int> _secondaryLength;
	private readonly StrategyParam<int> _stochasticLength;
	private readonly StrategyParam<int> _signalShift;
	private readonly StrategyParam<bool> _allowLongEntry;
	private readonly StrategyParam<bool> _allowShortEntry;
	private readonly StrategyParam<bool> _allowLongExit;
	private readonly StrategyParam<bool> _allowShortExit;
	private readonly StrategyParam<decimal> _highLevel;
	private readonly StrategyParam<decimal> _middleLevel;
	private readonly StrategyParam<decimal> _lowLevel;
	
	private DecimalLengthIndicator _primarySmoother = null!;
	private DecimalLengthIndicator _secondarySmoother = null!;
	private readonly List<decimal> _volatilityWindow = new();
	private readonly List<decimal> _mainHistory = new();
	
	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Smoothing method applied to the high-low spread.
	/// </summary>
	public SmoothMethods PrimaryMethod
	{
		get => _primaryMethod.Value;
		set => _primaryMethod.Value = value;
	}
	
	/// <summary>
	/// Length of the primary smoothing moving average.
	/// </summary>
	public int PrimaryLength
	{
		get => _primaryLength.Value;
		set => _primaryLength.Value = value;
	}
	
	/// <summary>
	/// Smoothing method applied to the stochastic ratio.
	/// </summary>
	public SmoothMethods SecondaryMethod
	{
		get => _secondaryMethod.Value;
		set => _secondaryMethod.Value = value;
	}
	
	/// <summary>
	/// Length of the secondary smoothing moving average.
	/// </summary>
	public int SecondaryLength
	{
		get => _secondaryLength.Value;
		set => _secondaryLength.Value = value;
	}
	
	/// <summary>
	/// Lookback for calculating the stochastic style normalization.
	/// </summary>
	public int StochasticLength
	{
		get => _stochasticLength.Value;
		set => _stochasticLength.Value = value;
	}
	
	/// <summary>
	/// Number of completed candles used as signal shift.
	/// </summary>
	public int SignalShift
	{
		get => _signalShift.Value;
		set => _signalShift.Value = value;
	}
	
	/// <summary>
	/// Enable opening of long positions.
	/// </summary>
	public bool AllowLongEntry
	{
		get => _allowLongEntry.Value;
		set => _allowLongEntry.Value = value;
	}
	
	/// <summary>
	/// Enable opening of short positions.
	/// </summary>
	public bool AllowShortEntry
	{
		get => _allowShortEntry.Value;
		set => _allowShortEntry.Value = value;
	}
	
	/// <summary>
	/// Enable closing of long positions on indicator reversal.
	/// </summary>
	public bool AllowLongExit
	{
		get => _allowLongExit.Value;
		set => _allowLongExit.Value = value;
	}
	
	/// <summary>
	/// Enable closing of short positions on indicator reversal.
	/// </summary>
	public bool AllowShortExit
	{
		get => _allowShortExit.Value;
		set => _allowShortExit.Value = value;
	}
	
	/// <summary>
	/// Upper visual level for the oscillator.
	/// </summary>
	public decimal HighLevel
	{
		get => _highLevel.Value;
		set => _highLevel.Value = value;
	}
	
	/// <summary>
	/// Middle visual level for the oscillator.
	/// </summary>
	public decimal MiddleLevel
	{
		get => _middleLevel.Value;
		set => _middleLevel.Value = value;
	}
	
	/// <summary>
	/// Lower visual level for the oscillator.
	/// </summary>
	public decimal LowLevel
	{
		get => _lowLevel.Value;
		set => _lowLevel.Value = value;
	}
	
	/// <summary>
	/// Initializes a new instance of the <see cref="StochasticChaikinsVolatilityStrategy"/> class.
	/// </summary>
	public StochasticChaikinsVolatilityStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");
		
		_primaryMethod = Param(nameof(PrimaryMethod), SmoothMethods.Sma)
		.SetDisplay("Primary Method", "Smoothing applied to high-low spread", "Indicator")
		;
		
		_primaryLength = Param(nameof(PrimaryLength), 20)
		.SetGreaterThanZero()
		.SetDisplay("Primary Length", "Periods for primary smoothing", "Indicator")
		;
		
		_secondaryMethod = Param(nameof(SecondaryMethod), SmoothMethods.Jurik)
		.SetDisplay("Secondary Method", "Smoothing applied to stochastic ratio", "Indicator")
		;
		
		_secondaryLength = Param(nameof(SecondaryLength), 10)
		.SetGreaterThanZero()
		.SetDisplay("Secondary Length", "Periods for secondary smoothing", "Indicator")
		;
		
		_stochasticLength = Param(nameof(StochasticLength), 14)
		.SetGreaterThanZero()
		.SetDisplay("Stochastic Length", "Lookback for highest-lowest range", "Indicator")
		;
		
		_signalShift = Param(nameof(SignalShift), 1)
		.SetGreaterThanZero()
		.SetDisplay("Signal Shift", "Completed candles offset for signals", "Trading")
		;
		
		_allowLongEntry = Param(nameof(AllowLongEntry), true)
		.SetDisplay("Allow Long Entry", "Enable opening of buy trades", "Trading");
		
		_allowShortEntry = Param(nameof(AllowShortEntry), true)
		.SetDisplay("Allow Short Entry", "Enable opening of sell trades", "Trading");
		
		_allowLongExit = Param(nameof(AllowLongExit), true)
		.SetDisplay("Allow Long Exit", "Enable closing longs on reversal", "Trading");
		
		_allowShortExit = Param(nameof(AllowShortExit), true)
		.SetDisplay("Allow Short Exit", "Enable closing shorts on reversal", "Trading");
		
		_highLevel = Param(nameof(HighLevel), 70m)
		.SetDisplay("High Level", "Upper visual threshold", "Visualization");

		_middleLevel = Param(nameof(MiddleLevel), 50m)
		.SetDisplay("Middle Level", "Middle visual threshold", "Visualization");

		_lowLevel = Param(nameof(LowLevel), 30m)
		.SetDisplay("Low Level", "Lower visual threshold", "Visualization");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_primarySmoother = null!;
		_secondarySmoother = null!;
		_volatilityWindow.Clear();
		_mainHistory.Clear();
	}
	
	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		_primarySmoother = CreateSmoother(PrimaryMethod, PrimaryLength);
		_secondarySmoother = CreateSmoother(SecondaryMethod, SecondaryLength);
		
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
		return;
		
		var diff = candle.HighPrice - candle.LowPrice;
		var smoothedValue = _primarySmoother.Process(diff, candle.OpenTime, true);
		if (!smoothedValue.IsFormed)
		return;
		
		var smoothedDiff = smoothedValue.ToDecimal();
		UpdateQueue(_volatilityWindow, smoothedDiff, StochasticLength);
		
		if (_volatilityWindow.Count < StochasticLength)
		return;
		
		decimal highest = decimal.MinValue;
		decimal lowest = decimal.MaxValue;
		var count = _volatilityWindow.Count;
		for (var vi = 0; vi < count; vi++)
		{
			var value = _volatilityWindow[vi];
			if (value > highest)
			highest = value;
			if (value < lowest)
			lowest = value;
		}
		
		var priceStep = Security?.PriceStep ?? 0.0001m;
		if (priceStep <= 0m)
		priceStep = 0.0001m;
		
		var range = highest - lowest;
		var denominator = range < priceStep ? priceStep : range;
		var normalized = denominator == 0m ? 0m : (smoothedDiff - lowest) / denominator;
		if (normalized < 0m)
		normalized = 0m;
		else if (normalized > 1m)
		normalized = 1m;
		
		var scaled = normalized * 100m;
		var stochasticValue = _secondarySmoother.Process(scaled, candle.OpenTime, true);
		if (!stochasticValue.IsFormed)
		return;
		
		var main = stochasticValue.ToDecimal();
		AddHistory(main);
		
		var minHistory = SignalShift + 3;
		if (_mainHistory.Count < minHistory)
		return;
		
		var idx = SignalShift;
		var value0 = _mainHistory[idx];
		var value1 = _mainHistory[idx + 1];
		var value2 = _mainHistory[idx + 2];
		
		var buyClose = AllowLongExit && value1 > HighLevel && value1 < value2;
		var sellClose = AllowShortExit && value1 < LowLevel && value1 > value2;
		var buyOpen = AllowLongEntry && value1 < LowLevel && value1 > value2 && value0 <= value1;
		var sellOpen = AllowShortEntry && value1 > HighLevel && value1 < value2 && value0 >= value1;
		
		// proceed with trading logic
		
		if (Position > 0m && buyClose)
		{
			SellMarket();
		}
		else if (Position < 0m && sellClose)
		{
			BuyMarket();
		}

		if (buyOpen && Position <= 0m)
		{
			BuyMarket();
		}
		else if (sellOpen && Position >= 0m)
		{
			SellMarket();
		}
	}
	
	private void AddHistory(decimal value)
	{
		_mainHistory.Insert(0, value);
		var maxSize = SignalShift + 4;
		while (_mainHistory.Count > maxSize)
		_mainHistory.RemoveAt(_mainHistory.Count - 1);
	}
	
	private static void UpdateQueue(List<decimal> queue, decimal value, int length)
	{
		queue.Add(value);
		while (queue.Count > length)
		queue.RemoveAt(0);
	}
	
	private static DecimalLengthIndicator CreateSmoother(SmoothMethods method, int length)
	{
		return method switch
		{
			SmoothMethods.Sma => new SimpleMovingAverage { Length = length },
			SmoothMethods.Ema => new ExponentialMovingAverage { Length = length },
			SmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
			SmoothMethods.Lwma => new WeightedMovingAverage { Length = length },
			SmoothMethods.Jurik => new JurikMovingAverage { Length = length },
			_ => new SMA { Length = length },
		};
	}
	
	/// <summary>
	/// Available smoothing methods supported by the strategy.
	/// </summary>
	public enum SmoothMethods
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Sma,
		
		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Ema,
		
		/// <summary>
		/// Smoothed moving average (RMA/SMMA).
		/// </summary>
		Smma,
		
		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Lwma,
		
		/// <summary>
		/// Jurik moving average approximation.
		/// </summary>
		Jurik
	}
}