Ver en GitHub

Estrategia Oscilador de Peso Fractal

Descripción general

Esta estrategia replica el asesor experto "Exp_Fractal_WeightOscillator" agregando cuatro osciladores (RSI, Money Flow Index, Williams %R y DeMarker) en una única señal compuesta suavizada. El oscilador se compara con dos niveles horizontales (HighLevel/LowLevel) para activar operaciones largas o cortas en modo de seguimiento de tendencia o contratendencia. Todos los cálculos se realizan en el marco temporal de velas seleccionado y usan la API estándar de alto nivel de StockSharp.

Pila de indicadores

  • Índice de Fuerza Relativa – aplicado a la fuente de precio configurada.
  • Money Flow Index – calculado a partir del precio aplicado elegido y el volumen de velas.
  • Williams %R – calculado a partir de los valores de máximo/mínimo/cierre de la vela.
  • DeMarker – recreado a partir de los máximos y mínimos de las velas con un suavizador de media simple.
  • Suavizador de media móvil – postprocesamiento opcional de la suma ponderada (SMA, EMA, SMMA o LWMA).

El valor del oscilador compuesto es una media ponderada de los cuatro componentes. HighLevel y LowLevel definen zonas de sobrecompra/sobreventa. SignalBar controla cuántas barras completadas se inspeccionan al buscar un cruce para que pueda retrasar la ejecución en relación con la vela terminada más reciente.

Lógica de trading

TrendMode = Direct

  • Entrada larga / salida corta – cuando el oscilador cae desde por encima de LowLevel hasta por debajo o igual a LowLevel (BuyOpenEnabled y SellCloseEnabled deben ser verdaderos).
  • Entrada corta / salida larga – cuando el oscilador sube desde por debajo de HighLevel hasta por encima o igual a HighLevel (SellOpenEnabled y BuyCloseEnabled deben ser verdaderos).

TrendMode = Counter

  • Entrada larga / salida corta – activada por una ruptura al alza de HighLevel.
  • Entrada corta / salida larga – activada por una ruptura a la baja de LowLevel.

Las señales se evalúan en la barra especificada por SignalBar. Las reversiones de posición usan Volumen + |Posición| para neutralizar cualquier exposición existente.

Gestión de riesgos

Cuando se abre una nueva posición, la estrategia calcula niveles de stop-loss y toma de ganancias de precio fijo usando StopLossPoints y TakeProfitPoints. Los valores se multiplican por el MinPriceStep del instrumento. En cada vela completada se comprueba el mínimo/máximo contra estos objetivos; si se alcanzan, la posición se cierra inmediatamente y los rastreadores de riesgo internos se restablecen.

Parámetros

Nombre Descripción
TrendMode Seleccionar comportamiento directo (seguimiento de tendencia) o contratendencia.
SignalBar Número de barras cerradas hacia atrás usadas para la evaluación de señales.
Period Longitud base para RSI, MFI, Williams %R y DeMarker.
SmoothingLength Ventana para el suavizador de media móvil.
SmoothingMethod Tipo de media móvil (None, Sma, Ema, Smma, Lwma).
RsiPrice, MfiPrice Fuente de precio aplicada usada en los osciladores componentes.
MfiVolume Tipo de volumen para MFI (tick y real ambos usan volumen de velas).
RsiWeight, MfiWeight, WprWeight, DeMarkerWeight Pesos relativos en el oscilador compuesto.
HighLevel, LowLevel Umbrales superior e inferior para cruces de nivel.
BuyOpenEnabled, SellOpenEnabled Habilitar entradas largas o cortas.
BuyCloseEnabled, SellCloseEnabled Permitir cerrar posiciones existentes en señales opuestas.
StopLossPoints, TakeProfitPoints Distancias de protección en pasos de precio (0 deshabilita el nivel).
CandleType Marco temporal de las velas pasadas a la estrategia.
Volume (Propiedad de estrategia) Tamaño de operación usado para entradas (las reversiones de posición añaden la posición absoluta).

Notas de uso

  • SignalBar = 1 reproduce el comportamiento del experto original usando la última barra completamente cerrada. Aumentar el valor retrasa las reacciones por barras adicionales.
  • SmoothingMethod permite desactivar el suavizado (None) o coincidir con los diferentes estilos de media móvil disponibles en la versión MQL.
  • La implementación del Money Flow Index siempre trabaja con el volumen total de la vela suministrado por el feed de datos. Por tanto, tanto las opciones Tick como Real se refieren al mismo valor agregado porque las velas de StockSharp no exponen contadores de tick separados por defecto.
  • Todos los comentarios en la fuente C# están escritos en inglés según se requiere.
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 }
		};
	}
}