Открыть на GitHub

Стратегия Stochastic Chaikin's Volatility

Общее описание

Эта стратегия — перенос на StockSharp советника MetaTrader Exp_Stochastic_Chaikins_Volatility. Алгоритм измеряет разброс между максимумом и минимумом свечи, сглаживает его выбранным типом скользящей средней, после чего нормализует результат подобно стохастическому осциллятору. Торговая логика соответствует оригиналу и носит контртрендовый характер: входы выполняются в момент разворота осциллятора, а закрытия — при восстановлении импульса в противоположную сторону.

Формирование индикатора

  1. Chaikin-подобная волатильность — разница High-Low сглаживается первой скользящей средней. Доступные методы сглаживания: SMA, EMA, SMMA (Wilder), LWMA и Jurik.
  2. Стохастическая нормализация — из последних Stochastic Length сглаженных значений берутся максимум и минимум, относительно которых рассчитывается текущая позиция в диапазоне 0–100.
  3. Вторичное сглаживание — коэффициент из п.2 дополнительно фильтруется второй скользящей средней (из того же списка методов), что даёт основную линию осциллятора. Сигнальная линия равна значению основной линии на предыдущей закрытой свече, как в исходном индикаторе.

Правила торговли

  • Входы
    • Покупка: когда осциллятор сформировал вершину и последняя закрытая свеча пересекла предыдущий максимум сверху вниз.
    • Продажа: когда осциллятор сформировал впадину и последняя свеча пересекла предыдущий минимум снизу вверх.
  • Выходы
    • Длинные позиции закрываются, если предыдущее значение осциллятора стало ниже ещё более раннего (импульс вниз).
    • Короткие позиции закрываются, если предыдущее значение стало выше ещё более раннего (импульс вверх).
  • Параметр Signal Shift задаёт, какую по счёту закрытую свечу анализировать. Значение 1 полностью повторяет логику MQL.

Параметры

Имя Описание
Candle Type Таймфрейм свечей для расчётов (по умолчанию 4 часа).
Primary Method / Primary Length Тип и период первичной скользящей для сглаживания диапазона High-Low.
Secondary Method / Secondary Length Тип и период вторичного сглаживания нормализованного осциллятора.
Stochastic Length Размер окна для поиска минимума и максимума при нормализации.
Signal Shift Сдвиг по закрытым свечам для расчёта сигналов (минимум 1).
Allow Long/Short Entry Разрешение на открытие длинных / коротких позиций.
Allow Long/Short Exit Разрешение на закрытие позиций при развороте осциллятора.
High/Middle/Low Level Визуальные уровни из оригинального индикатора (на торговлю не влияют).

Особенности использования

  • В StockSharp используются стандартные индикаторы. Экзотические методы сглаживания из MQL (ParMA, VIDYA, AMA и т.д.) заменяются ближайшими доступными аналогами; для более плавного поведения выбирайте вариант Jurik.
  • Размер позиции определяется параметром Volume базового класса. Системы стоп-лосса и тейк-профита из MQL-библиотеки не переносятся — выходы выполняются по сигналам осциллятора либо через внешнее управление риском (StartProtection).
  • Расчёты ведутся только по завершённым свечам. Убедитесь, что поставщик данных отдаёт выбранный таймфрейм с достаточной историей для прогрева обоих сглаживаний и стохастического окна.
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
	}
}