Открыть на GitHub

Стратегия Fractal Weight Oscillator

Обзор

Стратегия является конверсией советника "Exp_Fractal_WeightOscillator". Она объединяет четыре осциллятора (RSI, Money Flow Index, Williams %R и DeMarker) в единый сглаженный сигнал и сравнивает его с уровнями HighLevel / LowLevel. В зависимости от выбранного режима TrendMode стратегия работает по тренду или против него. Все расчёты выполняются на указанном таймфрейме свечей через высокоуровневый API StockSharp.

Набор индикаторов

  • RSI – рассчитывается по указанному источнику цены.
  • MFI – использует выбранную цену и объём свечи.
  • Williams %R – строится по максимумам/минимумам и закрытию.
  • DeMarker – пересобран вручную на основе максимумов/минимумов с простым сглаживанием.
  • Сглаживающая скользящая средняя – необязательный постфильтр (SMA, EMA, SMMA или LWMA).

Композитный осциллятор представляет собой взвешенное среднее четырёх компонентов. Уровни HighLevel и LowLevel задают зоны перекупленности и перепроданности, а параметр SignalBar позволяет смотреть сигнал на более старых закрытых барах.

Торговая логика

TrendMode = Direct (по тренду)

  • Покупка / закрытие шорта – когда осциллятор опускается от значений выше LowLevel до уровня LowLevel или ниже (при включённых BuyOpenEnabled и SellCloseEnabled).
  • Продажа / закрытие лонга – когда осциллятор поднимается от значений ниже HighLevel до уровня HighLevel или выше (требуются SellOpenEnabled и BuyCloseEnabled).

TrendMode = Counter (против тренда)

  • Покупка / закрытие шорта – при пробое вверх уровня HighLevel.
  • Продажа / закрытие лонга – при пробое вниз уровня LowLevel.

Сигналы анализируются на баре, заданном SignalBar. При развороте позиции покупается/продаётся объём Volume + |Position|, чтобы сначала закрыть текущую позицию.

Управление рисками

После открытия позиции рассчитываются стоп-лосс и тейк-профит по параметрам StopLossPoints и TakeProfitPoints. Значения умножаются на MinPriceStep. На каждой завершённой свече проверяются минимум/максимум; при достижении стопа или цели позиция закрывается и внутренние уровни риска сбрасываются.

Параметры

Параметр Описание
TrendMode Режим торговли: по тренду или против тренда.
SignalBar Сколько закрытых баров назад анализируется сигнал.
Period Базовый период для RSI, MFI, Williams %R и DeMarker.
SmoothingLength Длина окна сглаживающей скользящей средней.
SmoothingMethod Тип сглаживания (None, Sma, Ema, Smma, Lwma).
RsiPrice, MfiPrice Источники цены для компонентов.
MfiVolume Тип объёма для MFI (tick/real используют объём свечи).
RsiWeight, MfiWeight, WprWeight, DeMarkerWeight Вес каждого индикатора в суммарном осцилляторе.
HighLevel, LowLevel Верхний и нижний порог для поиска сигналов.
BuyOpenEnabled, SellOpenEnabled Разрешение на открытие лонгов/шортов.
BuyCloseEnabled, SellCloseEnabled Разрешение закрывать позиции при обратном сигнале.
StopLossPoints, TakeProfitPoints Расстояние до стоп-лосса и тейк-профита (в шагах цены, 0 отключает уровень).
CandleType Тип/таймфрейм обрабатываемых свечей.
Volume (свойство стратегии) Объём сделки; при развороте добавляется абсолютное значение текущей позиции.

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

  • Значение SignalBar = 1 соответствует оригинальному советнику и использует последний полностью сформированный бар. Увеличение значения добавляет задержку для подтверждения сигналов.
  • SmoothingMethod позволяет отключить сглаживание (None) или подобрать тип скользящей средней, аналогичный MQL-версии.
  • Рассчёт MFI всегда использует совокупный объём свечи из потока данных. Отдельный счётчик тиков в стандартных свечах StockSharp недоступен, поэтому варианты Tick и Real работают с одним и тем же значением.
  • Все комментарии в исходнике на английском языке согласно требованиям.
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;

using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on the Fractal Weight Oscillator indicator.
/// Combines RSI, MFI, Williams %R and DeMarker into a smoothed oscillator
/// and trades level crossings in direct or counter-trend mode.
/// </summary>
public class FractalWeightOscillatorStrategy : Strategy
{
	/// <summary>
	/// Volume source used for the MFI component.
	/// </summary>
	public enum MfiVolumeTypes
	{
		/// <summary>
		/// Tick volume.
		/// </summary>
		Tick,
		/// <summary>
		/// Real traded volume.
		/// </summary>
		Real
	}

	public enum TrendModes
	{
		/// <summary>
		/// Follow the trend.
		/// </summary>
		Direct,
		/// <summary>
		/// Trade against the trend.
		/// </summary>
		Counter
	}

	public enum SmoothingMethods
	{
		/// <summary>
		/// No smoothing.
		/// </summary>
		None,
		/// <summary>
		/// Simple Moving Average.
		/// </summary>
		Sma,
		/// <summary>
		/// Exponential Moving Average.
		/// </summary>
		Ema,
		/// <summary>
		/// Smoothed Moving Average.
		/// </summary>
		Smma,
		/// <summary>
		/// Linear Weighted Moving Average.
		/// </summary>
		Lwma
	}

	public enum AppliedPrice
	{
		None,
		Open,
		High,
		Low,
		Close,
		Median,
		Typical,
		Weighted,
		Simple,
		Quarter,
		TrendFollow0,
		TrendFollow1,
		DeMark
	}

	private readonly StrategyParam<TrendModes> _trendMode;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _smoothingLength;
	private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
	private readonly StrategyParam<AppliedPrice> _rsiPrice;
	private readonly StrategyParam<AppliedPrice> _mfiPrice;
	private readonly StrategyParam<MfiVolumeTypes> _mfiVolumeType;
	private readonly StrategyParam<decimal> _rsiWeight;
	private readonly StrategyParam<decimal> _mfiWeight;
	private readonly StrategyParam<decimal> _wprWeight;
	private readonly StrategyParam<decimal> _deMarkerWeight;
	private readonly StrategyParam<decimal> _highLevel;
	private readonly StrategyParam<decimal> _lowLevel;
	private readonly StrategyParam<bool> _buyOpenEnabled;
	private readonly StrategyParam<bool> _sellOpenEnabled;
	private readonly StrategyParam<bool> _buyCloseEnabled;
	private readonly StrategyParam<bool> _sellCloseEnabled;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi = null!;
	private RelativeStrengthIndex _williams = null!;
	private DecimalLengthIndicator _smoother;
	private SimpleMovingAverage _deMaxSma = null!;
	private SimpleMovingAverage _deMinSma = null!;

	private readonly List<decimal> _oscillatorHistory = new();
	private decimal _previousHigh;
	private decimal _previousLow;
	private bool _hasPreviousCandle;
	private readonly Queue<decimal> _positiveFlow = new();
	private readonly Queue<decimal> _negativeFlow = new();
	private decimal _previousTypical;
	private bool _hasPreviousTypical;
	private decimal _positiveSum;
	private decimal _negativeSum;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private DateTime _currentTime;

	/// <summary>
	/// Trading direction mode.
	/// </summary>
	public TrendModes TrendMode
	{
		get => _trendMode.Value;
		set => _trendMode.Value = value;
	}

	/// <summary>
	/// Number of closed bars used for signal evaluation.
	/// </summary>
	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

	/// <summary>
	/// Base period for all component oscillators.
	/// </summary>
	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	/// <summary>
	/// Smoothing window applied to the combined oscillator.
	/// </summary>
	public int SmoothingLength
	{
		get => _smoothingLength.Value;
		set => _smoothingLength.Value = value;
	}

	/// <summary>
	/// Type of moving average used for smoothing.
	/// </summary>
	public SmoothingMethods SmoothingMethod
	{
		get => _smoothingMethod.Value;
		set => _smoothingMethod.Value = value;
	}

	/// <summary>
	/// Applied price source for RSI calculation.
	/// </summary>
	public AppliedPrice RsiPrice
	{
		get => _rsiPrice.Value;
		set => _rsiPrice.Value = value;
	}

	/// <summary>
	/// Applied price source for MFI calculation.
	/// </summary>
	public AppliedPrice MfiPrice
	{
		get => _mfiPrice.Value;
		set => _mfiPrice.Value = value;
	}

	/// <summary>
	/// Volume type used by the MFI component.
	/// </summary>
	public MfiVolumeTypes MfiVolume
	{
		get => _mfiVolumeType.Value;
		set => _mfiVolumeType.Value = value;
	}

	/// <summary>
	/// Weight of the RSI contribution.
	/// </summary>
	public decimal RsiWeight
	{
		get => _rsiWeight.Value;
		set => _rsiWeight.Value = value;
	}

	/// <summary>
	/// Weight of the MFI contribution.
	/// </summary>
	public decimal MfiWeight
	{
		get => _mfiWeight.Value;
		set => _mfiWeight.Value = value;
	}

	/// <summary>
	/// Weight of the Williams %R contribution.
	/// </summary>
	public decimal WprWeight
	{
		get => _wprWeight.Value;
		set => _wprWeight.Value = value;
	}

	/// <summary>
	/// Weight of the DeMarker contribution.
	/// </summary>
	public decimal DeMarkerWeight
	{
		get => _deMarkerWeight.Value;
		set => _deMarkerWeight.Value = value;
	}

	/// <summary>
	/// Upper threshold of the oscillator.
	/// </summary>
	public decimal HighLevel
	{
		get => _highLevel.Value;
		set => _highLevel.Value = value;
	}

	/// <summary>
	/// Lower threshold of the oscillator.
	/// </summary>
	public decimal LowLevel
	{
		get => _lowLevel.Value;
		set => _lowLevel.Value = value;
	}

	/// <summary>
	/// Allow opening long positions.
	/// </summary>
	public bool BuyOpenEnabled
	{
		get => _buyOpenEnabled.Value;
		set => _buyOpenEnabled.Value = value;
	}

	/// <summary>
	/// Allow opening short positions.
	/// </summary>
	public bool SellOpenEnabled
	{
		get => _sellOpenEnabled.Value;
		set => _sellOpenEnabled.Value = value;
	}

	/// <summary>
	/// Allow closing long positions on opposite signals.
	/// </summary>
	public bool BuyCloseEnabled
	{
		get => _buyCloseEnabled.Value;
		set => _buyCloseEnabled.Value = value;
	}

	/// <summary>
	/// Allow closing short positions on opposite signals.
	/// </summary>
	public bool SellCloseEnabled
	{
		get => _sellCloseEnabled.Value;
		set => _sellCloseEnabled.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in instrument points.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in instrument points.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public FractalWeightOscillatorStrategy()
	{
		_trendMode = Param(nameof(TrendMode), TrendModes.Direct)
		.SetDisplay("Trend Mode", "Follow trend or counter-trend", "Trading");

		_signalBar = Param(nameof(SignalBar), 1)
		.SetNotNegative()
		.SetDisplay("Signal Bar", "Offset for signal evaluation", "Trading");

		_period = Param(nameof(Period), 30)
		.SetGreaterThanZero()
		.SetDisplay("Period", "Length for component oscillators", "Indicators");

		_smoothingLength = Param(nameof(SmoothingLength), 30)
		.SetGreaterThanZero()
		.SetDisplay("Smoothing Length", "Window for smoothing", "Indicators");

		_smoothingMethod = Param(nameof(SmoothingMethod), SmoothingMethods.Smma)
		.SetDisplay("Smoothing Method", "Moving average type for smoothing", "Indicators");

		_rsiPrice = Param(nameof(RsiPrice), AppliedPrice.Close)
		.SetDisplay("RSI Price", "Applied price for RSI", "Indicators");

		_mfiPrice = Param(nameof(MfiPrice), AppliedPrice.Typical)
		.SetDisplay("MFI Price", "Applied price for MFI", "Indicators");

		_mfiVolumeType = Param(nameof(MfiVolume), MfiVolumeTypes.Tick)
		.SetDisplay("MFI Volume", "Volume source for MFI", "Indicators");

		_rsiWeight = Param(nameof(RsiWeight), 1m)
		.SetGreaterThanZero()
		.SetDisplay("RSI Weight", "Weight of RSI component", "Weights");

		_mfiWeight = Param(nameof(MfiWeight), 1m)
		.SetGreaterThanZero()
		.SetDisplay("MFI Weight", "Weight of MFI component", "Weights");

		_wprWeight = Param(nameof(WprWeight), 1m)
		.SetGreaterThanZero()
		.SetDisplay("WPR Weight", "Weight of Williams %R component", "Weights");

		_deMarkerWeight = Param(nameof(DeMarkerWeight), 1m)
		.SetGreaterThanZero()
		.SetDisplay("DeMarker Weight", "Weight of DeMarker component", "Weights");

		_highLevel = Param(nameof(HighLevel), 60m)
		.SetDisplay("High Level", "Upper oscillator threshold", "Trading");

		_lowLevel = Param(nameof(LowLevel), 40m)
		.SetDisplay("Low Level", "Lower oscillator threshold", "Trading");

		_buyOpenEnabled = Param(nameof(BuyOpenEnabled), true)
		.SetDisplay("Enable Long Entries", "Allow buying", "Trading");

		_sellOpenEnabled = Param(nameof(SellOpenEnabled), true)
		.SetDisplay("Enable Short Entries", "Allow selling", "Trading");

		_buyCloseEnabled = Param(nameof(BuyCloseEnabled), true)
		.SetDisplay("Close Long", "Allow long exit on signals", "Trading");

		_sellCloseEnabled = Param(nameof(SellCloseEnabled), true)
		.SetDisplay("Close Short", "Allow short exit on signals", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pts)", "Stop-loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
		.SetNotNegative()
		.SetDisplay("Take Profit (pts)", "Take-profit distance in points", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for processing", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_oscillatorHistory.Clear();
		_previousHigh = 0m;
		_previousLow = 0m;
		_hasPreviousCandle = false;
		_positiveFlow.Clear();
		_negativeFlow.Clear();
		_previousTypical = 0m;
		_hasPreviousTypical = false;
		_positiveSum = 0m;
		_negativeSum = 0m;
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_currentTime = default;
	}

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

		_rsi = new RelativeStrengthIndex { Length = Period };
		_williams = new RelativeStrengthIndex { Length = Period };
		_deMaxSma = new SMA { Length = Period };
		_deMinSma = new SMA { Length = Period };
		_smoother = CreateSmoother(SmoothingMethod, SmoothingLength);

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

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

		_currentTime = candle.OpenTime;
		// indicators are processed manually below

		var rsiInput = GetPrice(candle, RsiPrice);
		var rsiValue = _rsi.Process(new DecimalIndicatorValue(_rsi, rsiInput, candle.OpenTime) { IsFinal = true });
		var mfiInput = GetPrice(candle, MfiPrice);
		var wprValue = _williams.Process(new DecimalIndicatorValue(_williams, candle.ClosePrice, candle.OpenTime) { IsFinal = true });

		if (!rsiValue.IsFinal || !wprValue.IsFinal)
			return;

		var mfi = CalculateMfi(candle, mfiInput);
		if (mfi is null)
			return;

		var deMarker = CalculateDeMarker(candle);
		if (deMarker is null)
			return;

		var rsi = rsiValue.GetValue<decimal>();
		var mfiValue = mfi.Value;
		var wpr = wprValue.GetValue<decimal>();
		var totalWeight = RsiWeight + MfiWeight + WprWeight + DeMarkerWeight;

		if (totalWeight <= 0m)
			return;

		var weighted = (RsiWeight * rsi
			+ MfiWeight * mfiValue
			+ WprWeight * wpr
			+ DeMarkerWeight * (deMarker.Value * 100m)) / totalWeight;

		var smoothed = ApplySmoothing(weighted);
		if (smoothed is null)
			return;

		_oscillatorHistory.Add(smoothed.Value);
		TrimHistory();

		if (_oscillatorHistory.Count < SignalBar + 2)
			return;

		var currentIndex = _oscillatorHistory.Count - 1 - SignalBar;
		if (currentIndex <= 0)
			return;

		var current = _oscillatorHistory[currentIndex];
		var previous = _oscillatorHistory[currentIndex - 1];

		CheckRisk(candle);

		var crossBelowLow = previous > LowLevel && current <= LowLevel;
		var crossAboveHigh = previous < HighLevel && current >= HighLevel;

		var openBuy = false;
		var closeBuy = false;
		var openSell = false;
		var closeSell = false;

		if (TrendMode == TrendModes.Direct)
		{
			if (crossBelowLow)
			{
				openBuy = BuyOpenEnabled;
				closeSell = SellCloseEnabled;
			}

			if (crossAboveHigh)
			{
				openSell = SellOpenEnabled;
				closeBuy = BuyCloseEnabled;
			}
		}
		else
		{
			if (crossBelowLow)
			{
				openSell = SellOpenEnabled;
				closeBuy = BuyCloseEnabled;
			}

			if (crossAboveHigh)
			{
				openBuy = BuyOpenEnabled;
				closeSell = SellCloseEnabled;
			}
		}

		if (closeBuy && Position > 0)
		{
			SellMarket();
			ResetRisk();
		}

		if (closeSell && Position < 0)
		{
			BuyMarket();
			ResetRisk();
		}

		if (openBuy && Position <= 0)
		{
			var volume = Volume + Math.Abs(Position);
			BuyMarket();
			SetRiskLevels(candle, Sides.Buy);
		}
		else if (openSell && Position >= 0)
		{
			var volume = Volume + Math.Abs(Position);
			SellMarket();
			SetRiskLevels(candle, Sides.Sell);
		}
	}

	private decimal? ApplySmoothing(decimal value)
	{
		if (_smoother is null)
			return value;

		var smoothed = _smoother.Process(new DecimalIndicatorValue(_smoother, value, _currentTime) { IsFinal = true });
		return smoothed.IsFinal ? smoothed.GetValue<decimal>() : null;
	}

	private decimal? CalculateDeMarker(ICandleMessage candle)
	{
		if (!_hasPreviousCandle)
		{
			_previousHigh = candle.HighPrice;
			_previousLow = candle.LowPrice;
			_hasPreviousCandle = true;
			return null;
		}

		var deMax = Math.Max(candle.HighPrice - _previousHigh, 0m);
		var deMin = Math.Max(_previousLow - candle.LowPrice, 0m);

		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;

		var deMaxValue = _deMaxSma.Process(new DecimalIndicatorValue(_deMaxSma, deMax, _currentTime) { IsFinal = true });
		var deMinValue = _deMinSma.Process(new DecimalIndicatorValue(_deMinSma, deMin, _currentTime) { IsFinal = true });

		if (!deMaxValue.IsFinal || !deMinValue.IsFinal)
			return null;

		var maxAvg = deMaxValue.GetValue<decimal>();
		var minAvg = deMinValue.GetValue<decimal>();
		var denom = maxAvg + minAvg;

		if (denom == 0m)
			return 0.5m;

		return maxAvg / denom;
	}

	private decimal? CalculateMfi(ICandleMessage candle, decimal price)
	{
		var volume = GetVolume(candle);

		if (!_hasPreviousTypical)
		{
			_previousTypical = price;
			_hasPreviousTypical = true;
			_positiveFlow.Clear();
			_negativeFlow.Clear();
			_positiveSum = 0m;
			_negativeSum = 0m;
			return null;
		}

		var flow = price * volume;
		var positive = price > _previousTypical ? flow : 0m;
		var negative = price < _previousTypical ? flow : 0m;

		_previousTypical = price;

		_positiveSum += positive;
		_negativeSum += negative;
		_positiveFlow.Enqueue(positive);
		_negativeFlow.Enqueue(negative);

		if (_positiveFlow.Count > Period)
		{
			_positiveSum -= _positiveFlow.Dequeue();
			_negativeSum -= _negativeFlow.Dequeue();
		}

		if (_positiveFlow.Count < Period)
			return null;

		if (_negativeSum == 0m)
			return 100m;

		var ratio = _positiveSum / _negativeSum;
		return 100m - 100m / (1m + ratio);
	}

	private void TrimHistory()
	{
		var maxSize = SignalBar + Math.Max(Period, SmoothingLength) + 5;
		if (_oscillatorHistory.Count <= maxSize)
			return;

		var remove = _oscillatorHistory.Count - maxSize;
		_oscillatorHistory.RemoveRange(0, remove);
	}

	private void CheckRisk(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
				ResetRisk();
				return;
			}

			if (_takePrice is decimal take && candle.HighPrice >= take)
			{
				SellMarket();
				ResetRisk();
			}
		}
		else if (Position < 0)
		{
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
				ResetRisk();
				return;
			}

			if (_takePrice is decimal take && candle.LowPrice <= take)
			{
				BuyMarket();
				ResetRisk();
			}
		}
	}

	private void SetRiskLevels(ICandleMessage candle, Sides side)
	{
		_entryPrice = candle.ClosePrice;

		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
		{
			_stopPrice = null;
			_takePrice = null;
			return;
		}

		_stopPrice = StopLossPoints > 0
			? side == Sides.Buy
				? _entryPrice - step * StopLossPoints
				: _entryPrice + step * StopLossPoints
			: null;

		_takePrice = TakeProfitPoints > 0
			? side == Sides.Buy
				? _entryPrice + step * TakeProfitPoints
				: _entryPrice - step * TakeProfitPoints
			: null;
	}

	private void ResetRisk()
	{
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
	}

	private decimal GetPrice(ICandleMessage candle, AppliedPrice price)
	{
		return price switch
		{
			AppliedPrice.Open => candle.OpenPrice,
			AppliedPrice.High => candle.HighPrice,
			AppliedPrice.Low => candle.LowPrice,
			AppliedPrice.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPrice.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPrice.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
			AppliedPrice.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
			AppliedPrice.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
			AppliedPrice.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice
				: candle.ClosePrice < candle.OpenPrice ? candle.LowPrice
				: candle.ClosePrice,
			AppliedPrice.TrendFollow1 => candle.ClosePrice > candle.OpenPrice
				? (candle.HighPrice + candle.ClosePrice) / 2m
				: candle.ClosePrice < candle.OpenPrice
					? (candle.LowPrice + candle.ClosePrice) / 2m
					: candle.ClosePrice,
			AppliedPrice.DeMark => CalculateDeMarkPrice(candle),
			_ => candle.ClosePrice,
		};
	}

	private static decimal CalculateDeMarkPrice(ICandleMessage candle)
	{
		var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
		if (candle.ClosePrice < candle.OpenPrice)
			res = (res + candle.LowPrice) / 2m;
		else if (candle.ClosePrice > candle.OpenPrice)
			res = (res + candle.HighPrice) / 2m;
		else
			res = (res + candle.ClosePrice) / 2m;
		return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
	}

	private decimal GetVolume(ICandleMessage candle)
	{
		return candle.TotalVolume;
	}

	private static DecimalLengthIndicator CreateSmoother(SmoothingMethods method, int length)
	{
		return method switch
		{
			SmoothingMethods.None => null,
			SmoothingMethods.Sma => new SMA { Length = length },
			SmoothingMethods.Ema => new EMA { Length = length },
			SmoothingMethods.Smma => new SmoothedMovingAverage { Length = length },
			SmoothingMethods.Lwma => new WeightedMovingAverage { Length = length },
			_ => new SmoothedMovingAverage { Length = length }
		};
	}
}