Auf GitHub ansehen

Stochastic Chaikin's Volatilität Strategie

Überblick

Diese Strategie ist ein StockSharp-Port des MetaTrader-Expertenberaters Exp_Stochastic_Chaikins_Volatility. Sie analysiert die Spanne zwischen Hoch- und Tiefpreisen, glättet diese Volatilität mit einem konfigurierbaren gleitenden Durchschnitt und normalisiert das Ergebnis dann mit einem Stochastic-ähnlichen Oszillator. Die Handelsentscheidungen folgen der ursprünglichen Gegentrend-Logik: Die Strategie sucht nach Wendepunkten im Oszillator, um kurzfristige Extreme zu handeln, während bestehende Positionen optional geschlossen werden, wenn der Schwung sich umkehrt.

Indikatoraufbau

  1. Chaikin-Volatilität – die Differenz zwischen Kerzenhoch und -tief wird mit dem primären gleitenden Durchschnitt geglättet. Unterstützte Glättungsmethoden sind:
    • Einfach (SMA)
    • Exponentiell (EMA)
    • Geglättet/Wilder (SMMA)
    • Linear gewichtet (LWMA)
    • Jurik (JMA-Approximation)
  2. Stochastische Normalisierung – die letzten Stochastic Length geglätteten Werte definieren den höchsten und niedrigsten Bereich. Der aktuelle geglättete Wert wird mit diesem Fenster in einen Bereich von 0–100 normalisiert.
  3. Sekundäre Glättung – ein zweiter gleitender Durchschnitt (Methode aus derselben Liste wählbar) wird auf den normalisierten Wert angewendet, um die Haupt-Oszillatorlinie zu erhalten. Intern ist die Signallinie einfach der Oszillatorwert der vorherigen abgeschlossenen Kerze, was das MQL-Indikator-Buffer-Verhalten repliziert.

Handelsstrategie

  • Einstieg
    • Kaufen: wenn der Hauptoszillator ein niedrigeres Hoch gebildet hat (vorheriger Wert größer als sein eigener Vorgängerwert, aktueller Wert kreuzt unter diesen vorherigen Wert). Dies spiegelt den ursprünglichen konträren Long-Auslöser des EA wider.
    • Verkaufen: wenn der Oszillator ein höheres Tief gebildet hat (vorheriger Wert kleiner als sein eigener Vorgängerwert, aktueller Wert kreuzt über diesen vorherigen Wert).
  • Ausstieg
    • Long-Positionen schließen, wenn der vorherige Oszillatorwert unter seinen älteren Wert fällt (Abwärtsschwung taucht wieder auf).
    • Short-Positionen schließen, wenn der vorherige Oszillatorwert über seinen älteren Wert steigt.
  • Die Signalauswertung verwendet den Parameter Signal Shift, um abgeschlossene Kerzen zu inspizieren. Die Standardwerte emulieren die MQL-Einstellung von 1 Bar.

Parameter

Name Beschreibung
Candle Type Zeitrahmen für alle Berechnungen (Standard: 4-Stunden-Zeitkerzen).
Primary Method / Primary Length Gleitender Durchschnittstyp und -länge zur Glättung der Hoch-Tief-Spanne.
Secondary Method / Secondary Length Gleitender Durchschnittstyp und -länge zur Glättung des normalisierten Oszillators.
Stochastic Length Rückblickfenster für den höchsten/niedrigsten Bereich im Normalisierungsschritt.
Signal Shift Anzahl der abgeschlossenen Kerzen zwischen der aktuellen Bar und der für die Signalauswertung verwendeten Bar. Muss ≥1 bleiben.
Allow Long/Short Entry Long- oder Short-Trades öffnen oder deaktivieren.
Allow Long/Short Exit Positionsschließung bei Oszillatorumkehr aktivieren oder deaktivieren.
High/Middle/Low Level Visuelle Referenzlevel aus dem ursprünglichen Indikator (kein direkter Handelseffekt).

Verwendungshinweise

  • Der StockSharp-Port behält das ursprüngliche Gegentrend-Verhalten bei, verwendet aber StockSharp-gleitende Durchschnitte. Exotische Methoden aus der MQL-Bibliothek (ParMA, VIDYA, AMA usw.) werden zur nächsten verfügbaren Glättungsoption zugeordnet; wählen Sie Jurik für eine genauere Annäherung, wenn nötig.
  • Die Positionsgröße folgt der Volume-Eigenschaft der Basisstrategie. Stop-Loss- und Take-Profit-Management aus der MQL-Hilfsbibliothek wird nicht repliziert; Exits stützen sich auf Oszillatorumkehrungen oder externes Risikomanagement wie StartProtection.
  • Signale werden nur bei abgeschlossenen Kerzen berechnet. Stellen Sie sicher, dass der Datenfeed den ausgewählten Candle Type mit ausreichend Geschichte bereitstellt, damit sich beide Glättungsstufen und das Stochastic-Fenster aufwärmen können.
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
	}
}