Ver no GitHub

Estratégia Oscilador de Peso Fractal

Visão Geral

Esta estratégia replica o assessor especialista "Exp_Fractal_WeightOscillator" agregando quatro osciladores (RSI, Money Flow Index, Williams %R e DeMarker) em um único sinal composto suavizado. O oscilador é comparado com dois níveis horizontais (HighLevel/LowLevel) para acionar negociações compradas ou vendidas no modo de seguimento de tendência ou contratendência. Todos os cálculos são realizados no período de velas selecionado e usam a API de alto nível padrão do StockSharp.

Pilha de indicadores

  • Índice de Força Relativa – aplicado à fonte de preço configurada.
  • Money Flow Index – calculado a partir do preço aplicado escolhido e do volume de velas.
  • Williams %R – calculado a partir dos valores de máximo/mínimo/fechamento da vela.
  • DeMarker – recriado a partir das máximas e mínimas das velas com um suavizador de média simples.
  • Suavizador de média móvel – pós-processamento opcional da soma ponderada (SMA, EMA, SMMA ou LWMA).

O valor do oscilador composto é uma média ponderada dos quatro componentes. HighLevel e LowLevel definem zonas de sobrecompra/sobrevenda. SignalBar controla quantas barras completadas são inspecionadas ao procurar um cruzamento para que você possa atrasar a execução em relação à vela mais recente terminada.

Lógica de trading

TrendMode = Direct

  • Entrada comprada / saída vendida – quando o oscilador cai de acima de LowLevel para abaixo ou igual a LowLevel (BuyOpenEnabled e SellCloseEnabled devem ser verdadeiros).
  • Entrada vendida / saída comprada – quando o oscilador sobe de abaixo de HighLevel para acima ou igual a HighLevel (SellOpenEnabled e BuyCloseEnabled devem ser verdadeiros).

TrendMode = Counter

  • Entrada comprada / saída vendida – acionada por um rompimento para cima de HighLevel.
  • Entrada vendida / saída comprada – acionada por um rompimento para baixo de LowLevel.

Os sinais são avaliados na barra especificada por SignalBar. As reversões de posição usam Volume + |Posição| para neutralizar qualquer exposição existente.

Gestão de risco

Quando uma nova posição é aberta, a estratégia calcula níveis fixos de preço de stop-loss e take profit usando StopLossPoints e TakeProfitPoints. Os valores são multiplicados pelo MinPriceStep do instrumento. Em cada vela completada, o mínimo/máximo é verificado contra esses alvos; se atingidos, a posição é fechada imediatamente e os rastreadores internos de risco são redefinidos.

Parâmetros

Nome Descrição
TrendMode Selecionar comportamento direto (seguimento de tendência) ou contratendência.
SignalBar Número de barras fechadas para trás usadas para avaliação de sinal.
Period Comprimento base para RSI, MFI, Williams %R e DeMarker.
SmoothingLength Janela para o suavizador de média móvel.
SmoothingMethod Tipo de média móvel (None, Sma, Ema, Smma, Lwma).
RsiPrice, MfiPrice Fonte de preço aplicada usada nos osciladores componentes.
MfiVolume Tipo de volume para MFI (tick e real ambos usam volume de velas).
RsiWeight, MfiWeight, WprWeight, DeMarkerWeight Pesos relativos no oscilador composto.
HighLevel, LowLevel Limiares superior e inferior para cruzamentos de nível.
BuyOpenEnabled, SellOpenEnabled Habilitar entradas compradas ou vendidas.
BuyCloseEnabled, SellCloseEnabled Permitir fechar posições existentes em sinais opostos.
StopLossPoints, TakeProfitPoints Distâncias de proteção em passos de preço (0 desabilita o nível).
CandleType Período das velas passadas à estratégia.
Volume (Propriedade de estratégia) Tamanho da negociação usado para entradas (as reversões de posição adicionam a posição absoluta).

Notas de uso

  • SignalBar = 1 reproduz o comportamento do especialista original usando a última barra completamente fechada. Aumentar o valor atrasa as reações por barras adicionais.
  • SmoothingMethod permite desativar a suavização (None) ou corresponder aos diferentes estilos de média móvel disponíveis na versão MQL.
  • A implementação do Money Flow Index sempre trabalha com o volume total da vela fornecido pelo feed de dados. Portanto, ambas as opções Tick e Real se referem ao mesmo valor agregado porque as velas do StockSharp não expõem contadores de tick separados por padrão.
  • Todos os comentários no fonte C# estão escritos em inglês, conforme necessário.
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 }
		};
	}
}