Auf GitHub ansehen

Stochastic Chaikin's Volatility Strategy

Overview

This strategy is a StockSharp port of the MetaTrader expert advisor Exp_Stochastic_Chaikins_Volatility. It analyses the spread between high and low prices, smooths that volatility with a configurable moving average, and then normalizes the result using a stochastic-like oscillator. Trading decisions follow the original counter-trend logic: the strategy looks for turning points in the oscillator to fade short-term extremes while optionally closing existing positions when momentum flips back.

Indicator construction

  1. Chaikin-style volatility – the difference between the candle high and low is smoothed with the primary moving average. Supported smoothing methods are:
    • Simple (SMA)
    • Exponential (EMA)
    • Smoothed/Wilder (SMMA)
    • Linear weighted (LWMA)
    • Jurik (JMA approximation)
  2. Stochastic normalization – the most recent Stochastic Length smoothed values define the highest and lowest range. The current smoothed value is normalized into a 0–100 range using that window.
  3. Secondary smoothing – a second moving average (method selectable from the same list) is applied to the normalized value to obtain the main oscillator line. Internally the signal line is simply the oscillator value from the previous completed candle, replicating the MQL indicator buffer behaviour.

Trading logic

  • Entry
    • Buy: when the main oscillator formed a lower high (previous value greater than its own prior value, current value crosses below that previous value). This mirrors the original EA's contrarian long trigger.
    • Sell: when the oscillator formed a higher low (previous value lower than its own prior value, current value crosses above that previous value).
  • Exit
    • Long positions close when the previous oscillator value moves below its older value (downward momentum reappears).
    • Short positions close when the previous oscillator value rises above its older value.
  • Signal evaluation uses the Signal Shift parameter to inspect completed candles. The defaults emulate the MQL setting of 1 bar.

Parameters

Name Description
Candle Type Timeframe used for all calculations (default 4-hour time candles).
Primary Method / Primary Length Moving-average type and length for smoothing the high–low spread.
Secondary Method / Secondary Length Moving-average type and length for smoothing the normalized oscillator.
Stochastic Length Lookback window for the highest/lowest range used in the normalization step.
Signal Shift Number of completed candles between the current bar and the bar used for signal evaluation. Must stay ≥1.
Allow Long/Short Entry Enable or disable opening long or short trades.
Allow Long/Short Exit Enable or disable position closing when the oscillator reverses.
High/Middle/Low Level Visual guide levels reproduced from the original indicator (no direct trading effect).

Usage notes

  • The StockSharp port keeps the original counter-trend behaviour but uses StockSharp moving averages. Exotic methods from the MQL library (ParMA, VIDYA, AMA, etc.) are mapped to the nearest available smoothing option; choose Jurik for a closer approximation when needed.
  • Position sizing follows the base strategy Volume property. Stop-loss and take-profit management from the MQL helper library is not replicated; exits rely on oscillator reversals or external risk management such as StartProtection.
  • Signals are calculated on finished candles only. Ensure that the data feed provides the selected Candle Type with sufficient history so that both smoothing stages and the stochastic window can warm up.
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
	}
}