Ver en GitHub

Estrategia Natuseko Protrader 4H

Descripción general

La estrategia Natuseko Protrader 4H es una versión StockSharp del asesor experto MetaTrader 4 NatusekoProtrader4HStrategy. el original El robot combina medias móviles exponenciales, un oscilador MACD filtrado por Bollinger bandas, RSI umbrales y el Parabolic SAR para identifique velas de ruptura fuertes en el marco de tiempo de cuatro horas. Cuando aparece una vela calificada, el sistema se abre inmediatamente o espera un retroceso hacia el EMA rápida antes de entrar. Una vez posicionada, la estrategia realiza una toma de ganancias parcial y salidas completas. basado en señales RSI y Parabolic SAR, replicando el bloque de administración de dinero presente en el código MQL.

Lógica comercial

  1. Suscríbase al flujo de velas principal definido por CandleType (velas de 4 horas de forma predeterminada) y procese solo velas terminadas.
  2. Calcule tres medias móviles exponenciales (rápida, lenta y de tendencia) sobre los precios de cierre. Los tres tienen longitudes configurables.
  3. Alimente el indicador MACD (períodos rápido, lento y de señal tomados del EA) y aplique una media móvil simple más Bollinger bandas a la línea principal MACD. La línea media Bollinger actúa como nivel de referencia utilizado por la versión MQL.
  4. Calcule el RSI sobre los precios de cierre y el Parabolic SAR utilizando datos completos de velas. Estos indicadores impulsan tanto las entradas como las salidas.
  5. Detecte velas de configuración alcista cuando se cumplan todas las condiciones siguientes:
    • El EMA rápida está por encima tanto del lento como de la tendencia EMA.
    • RSI está por encima de RsiEntryLevel pero por debajo de RsiTakeProfitLong.
    • La línea principal MACD está por encima de su línea corta SMA y de la línea media Bollinger; el SMA también está por encima de la línea media.
    • El cuerpo de la vela es más grande que ambas sombras, lo que significa que la vela se cierra con fuerza en la dirección del movimiento.
    • Parabolic SAR se encuentra debajo del cierre de la vela.
  6. Detecte configuraciones bajistas utilizando los controles simétricos (EMA rápida a continuación, RSI entre RsiTakeProfitShort y RsiEntryLevel, valores MACD debajo de la línea media Bollinger, cuerpo de la vela bajista y SAR por encima del cierre).
  7. Si la vela calificada está demasiado lejos de la tendencia EMA (distancia por encima de DistanceThresholdPoints), establezca una bandera pendiente y espere una retroceso. Una entrada larga se activa una vez que el precio toca el EMA rápida mientras que RSI y SAR permanecen alineados con el escenario alcista; el La entrada corta funciona de manera análoga en los retrocesos al EMA rápida desde abajo.
  8. Cuando no se requiere un retroceso, la estrategia cierra cualquier exposición opuesta y abre una nueva posición con TradeVolume lotes. Stop Loss La ubicación sigue las reglas de EA: la primera preferencia se da a Parabolic SAR si UseSarStopLoss está habilitado; de lo contrario, la tendencia Se utiliza EMA. StopOffsetPoints se convierte en precio distancia con el paso del precio del instrumento y se aplica al nivel de parada.
  9. Mientras una posición larga está abierta, la estrategia recalcula continuamente el precio stop y gestiona las salidas:
    • Si el precio cae por debajo del stop, se cierra toda la posición.
    • Después de alcanzar al menos MinimumProfitPoints de ganancia (en puntos de instrumento), la estrategia puede cerrar la mitad de la posición cuando el RSI excede RsiTakeProfitLong o cuando el Parabolic SAR supera el precio (controlado por UseRsiTakeProfit y UseSarTakeProfit).
    • Una vez que el beneficio es adecuado y RSI vuelve a caer por debajo de RsiEntryLevel, se cierra la exposición larga restante.
  10. Las posiciones cortas reflejan las mismas reglas con los umbrales RSI invertidos y los cheques SAR invertidos en relación con el precio.

Gestión de posiciones

  • Las salidas parciales ocurren como máximo una vez por lado comercial. Después de cerrar la mitad de la posición, la estrategia espera la condición de salida total. (RSI cruzando nuevamente el nivel neutral o un hit de stop-loss).
  • Los precios de stop-loss se recalculan en cada vela utilizando el último valor Parabolic SAR o tendencia EMA para mantenerse alineados con la lógica MQL.
  • Cuando el tamaño de la posición vuelve a cero, el estado interno (indicadores de entrada pendiente, referencias de parada y marcadores de salida parcial) se restablece para que el tamaño de la posición vuelva a cero. La próxima operación comienza limpiamente.

Parámetros

Nombre Tipo Predeterminado Descripción
CandleType DataType plazo de 4 horas Plazo primario procesado por la estrategia.
TradeVolume decimal 0.1 Volumen de pedidos utilizado para las entradas.
FastEmaPeriod int 13 Longitud del filtro EMA rápida.
SlowEmaPeriod int 21 Longitud del filtro EMA más lento.
TrendEmaPeriod int 55 EMA se utiliza para comprobaciones a distancia y colocación de stop-loss.
MacdFastPeriod int 5 Longitud rápida EMA dentro del indicador MACD.
MacdSlowPeriod int 200 Longitud lenta de EMA dentro del indicador MACD.
MacdSignalPeriod int 1 Longitud de la media móvil de la señal dentro del indicador MACD.
BollingerPeriod int 20 Número de MACD muestras utilizadas para calcular Bollinger bandas.
BollingerWidth decimal 1 Multiplicador de desviación estándar para las Bandas MACD Bollinger.
MacdSmaPeriod int 3 Longitud del suavizado MACD SMA.
RsiPeriod int 21 Longitud del indicador RSI.
RsiEntryLevel decimal 50 Umbral neutral RSI compartido por las reglas de entrada y salida.
RsiTakeProfitLong decimal 65 RSI nivel que permite la toma de ganancias parcial para posiciones largas.
RsiTakeProfitShort decimal 35 RSI nivel que permite la toma de ganancias parcial para posiciones cortas.
DistanceThresholdPoints decimal 100 Distancia máxima en puntos del instrumento entre el precio y la tendencia EMA antes de que se retrase la entrada.
SarStep decimal 0.02 Paso de aceleración para el Parabolic SAR.
SarMaximum decimal 0.2 Aceleración máxima para el Parabolic SAR.
UseSarStopLoss bool false Utilice el Parabolic SAR para derivar la parada de protección.
UseTrendStopLoss bool true Utilice la tendencia EMA para derivar la parada de protección.
StopOffsetPoints int 0 Compensación adicional (en puntos) agregada al precio de parada de protección.
UseSarTakeProfit bool true Habilite salidas parciales cuando el precio cruce el Parabolic SAR.
UseRsiTakeProfit bool true Habilite las salidas parciales cuando RSI alcance el umbral de obtención de beneficios.
MinimumProfitPoints decimal 5 Beneficio mínimo (en puntos) antes de que se activen las reglas de toma de ganancias parcial o total.

Diferencias con el EA original

  • StockSharp negocia posiciones netas. Para emular el comportamiento de un solo ticket de MetaTrader, la estrategia cierra automáticamente lo contrario. exposición antes de abrir una nueva operación en la otra dirección.
  • Los asistentes de administración de dinero se implementan con órdenes de mercado en lugar de modificar órdenes individuales porque StockSharp no administra paradas por boleto. El efecto coincide con el EA: una salida parcial seguida de una salida final cuando el impulso de RSI se desvanece.
  • Los cálculos de distancia de precio se basan en el instrumento PriceStep. Si el valor no define un paso de precio, la estrategia asume un paso 1. Ajuste DistanceThresholdPoints y MinimumProfitPoints en consecuencia para instrumentos que utilizan diferentes tamaños de puntos.

Consejos de uso

  • Configure TradeVolume según el paso del lote del instrumento; el constructor también asigna el mismo valor a Strategy.Volume entonces Los métodos auxiliares utilizan el tamaño esperado.
  • Si las operaciones se retrasan con demasiada frecuencia porque las velas cierran lejos de la tendencia EMA, baje DistanceThresholdPoints o desactive el filtro poniéndolo a cero.
  • Se recomienda trazar la estrategia: el código dibuja velas, las tres EMA, RSI, Parabolic SAR y MACD Bollinger bandas para que puedas Confirme visualmente la lógica convertida.
  • Los parámetros MACD reflejan la combinación inusual de EA (rápido=5, lento=200, señal=1). Considere optimizarlos antes de publicarlos porque un período lento tan amplio produce valores muy suaves pero rezagados.
namespace StockSharp.Samples.Strategies;

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;

public class NatusekoProtrader4HStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _trendEmaPeriod;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;
	private readonly StrategyParam<int> _macdSmaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiEntryLevel;
	private readonly StrategyParam<decimal> _rsiTakeProfitLong;
	private readonly StrategyParam<decimal> _rsiTakeProfitShort;
	private readonly StrategyParam<decimal> _distanceThresholdPoints;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMaximum;
	private readonly StrategyParam<bool> _useSarStopLoss;
	private readonly StrategyParam<bool> _useTrendStopLoss;
	private readonly StrategyParam<int> _stopOffsetPoints;
	private readonly StrategyParam<bool> _useSarTakeProfit;
	private readonly StrategyParam<bool> _useRsiTakeProfit;
	private readonly StrategyParam<decimal> _minimumProfitPoints;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private ExponentialMovingAverage _trendEma;
	private MovingAverageConvergenceDivergence _macd;
	private BollingerBands _macdBands;
	private SimpleMovingAverage _macdSma;
	private RelativeStrengthIndex _rsi;
	private ParabolicSar _parabolicSar;

	private bool _waitingForLongEntry;
	private bool _waitingForShortEntry;
	private bool _longPartialExecuted;
	private bool _shortPartialExecuted;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;

	public NatusekoProtrader4HStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle type", "Primary timeframe processed by the strategy.", "General");

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade volume", "Default order size used for entries.", "Trading");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA period", "Length of the fast EMA filter.", "Indicator");

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA period", "Length of the slower EMA filter.", "Indicator");

		_trendEmaPeriod = Param(nameof(TrendEmaPeriod), 55)
			.SetGreaterThanZero()
			.SetDisplay("Trend EMA period", "Length of the trend EMA used for filters and stop loss.", "Indicator");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("MACD fast period", "Fast EMA length inside the MACD indicator.", "Indicator");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD slow period", "Slow EMA length inside the MACD indicator.", "Indicator");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 1)
			.SetGreaterThanZero()
			.SetDisplay("MACD signal period", "Signal moving average length for MACD.", "Indicator");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger period", "Number of MACD samples used for the Bollinger Bands.", "Indicator");

		_bollingerWidth = Param(nameof(BollingerWidth), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger width", "Standard deviation multiplier for the MACD Bollinger Bands.", "Indicator");

		_macdSmaPeriod = Param(nameof(MacdSmaPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("MACD SMA period", "Length of the moving average applied to the MACD line.", "Indicator");

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI period", "Length of the RSI filter.", "Indicator");

		_rsiEntryLevel = Param(nameof(RsiEntryLevel), 50m)
			.SetDisplay("RSI neutral level", "Central RSI threshold used for both entry and exit rules.", "Trading");

		_rsiTakeProfitLong = Param(nameof(RsiTakeProfitLong), 65m)
			.SetDisplay("RSI take profit long", "RSI value that triggers a partial exit for long positions.", "Trading");

		_rsiTakeProfitShort = Param(nameof(RsiTakeProfitShort), 35m)
			.SetDisplay("RSI take profit short", "RSI value that triggers a partial exit for short positions.", "Trading");

		_distanceThresholdPoints = Param(nameof(DistanceThresholdPoints), 100m)
			.SetNotNegative()
			.SetDisplay("Distance threshold", "Maximum distance in points between price and the trend EMA before delaying the entry.", "Trading");

		_sarStep = Param(nameof(SarStep), 0.02m)
			.SetGreaterThanZero()
			.SetDisplay("SAR step", "Acceleration step of the Parabolic SAR indicator.", "Indicator");

		_sarMaximum = Param(nameof(SarMaximum), 0.2m)
			.SetGreaterThanZero()
			.SetDisplay("SAR maximum", "Maximum acceleration of the Parabolic SAR indicator.", "Indicator");

		_useSarStopLoss = Param(nameof(UseSarStopLoss), false)
			.SetDisplay("Use SAR stop loss", "Whether the Parabolic SAR defines the protective stop price.", "Risk");

		_useTrendStopLoss = Param(nameof(UseTrendStopLoss), true)
			.SetDisplay("Use trend stop loss", "Whether the trend EMA defines the protective stop price.", "Risk");

		_stopOffsetPoints = Param(nameof(StopOffsetPoints), 0)
			.SetNotNegative()
			.SetDisplay("Stop offset", "Additional point offset added to the protective stop.", "Risk");

		_useSarTakeProfit = Param(nameof(UseSarTakeProfit), true)
			.SetDisplay("Use SAR take profit", "Enable partial exits when price crosses the Parabolic SAR.", "Risk");

		_useRsiTakeProfit = Param(nameof(UseRsiTakeProfit), true)
			.SetDisplay("Use RSI take profit", "Enable partial exits driven by RSI thresholds.", "Risk");

		_minimumProfitPoints = Param(nameof(MinimumProfitPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Minimum profit", "Minimum profit in points required before take-profit rules activate.", "Risk");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	public int TrendEmaPeriod
	{
		get => _trendEmaPeriod.Value;
		set => _trendEmaPeriod.Value = value;
	}

	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	public int MacdSmaPeriod
	{
		get => _macdSmaPeriod.Value;
		set => _macdSmaPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiEntryLevel
	{
		get => _rsiEntryLevel.Value;
		set => _rsiEntryLevel.Value = value;
	}

	public decimal RsiTakeProfitLong
	{
		get => _rsiTakeProfitLong.Value;
		set => _rsiTakeProfitLong.Value = value;
	}

	public decimal RsiTakeProfitShort
	{
		get => _rsiTakeProfitShort.Value;
		set => _rsiTakeProfitShort.Value = value;
	}

	public decimal DistanceThresholdPoints
	{
		get => _distanceThresholdPoints.Value;
		set => _distanceThresholdPoints.Value = value;
	}

	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	public decimal SarMaximum
	{
		get => _sarMaximum.Value;
		set => _sarMaximum.Value = value;
	}

	public bool UseSarStopLoss
	{
		get => _useSarStopLoss.Value;
		set => _useSarStopLoss.Value = value;
	}

	public bool UseTrendStopLoss
	{
		get => _useTrendStopLoss.Value;
		set => _useTrendStopLoss.Value = value;
	}

	public int StopOffsetPoints
	{
		get => _stopOffsetPoints.Value;
		set => _stopOffsetPoints.Value = value;
	}

	public bool UseSarTakeProfit
	{
		get => _useSarTakeProfit.Value;
		set => _useSarTakeProfit.Value = value;
	}

	public bool UseRsiTakeProfit
	{
		get => _useRsiTakeProfit.Value;
		set => _useRsiTakeProfit.Value = value;
	}

	public decimal MinimumProfitPoints
	{
		get => _minimumProfitPoints.Value;
		set => _minimumProfitPoints.Value = value;
	}

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

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

		_waitingForLongEntry = false;
		_waitingForShortEntry = false;
		_longPartialExecuted = false;
		_shortPartialExecuted = false;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;
	}

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

		Volume = TradeVolume;

		_fastEma = new EMA { Length = FastEmaPeriod };
		_slowEma = new EMA { Length = SlowEmaPeriod };
		_trendEma = new EMA { Length = TrendEmaPeriod };

		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = MacdFastPeriod },
			LongMa = { Length = MacdSlowPeriod }
		};

		_macdBands = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerWidth
		};

		_macdSma = new SMA { Length = MacdSmaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_parabolicSar = new ParabolicSar
		{
			Acceleration = SarStep,
			AccelerationMax = SarMaximum
		};

		_waitingForLongEntry = false;
		_waitingForShortEntry = false;
		_longPartialExecuted = false;
		_shortPartialExecuted = false;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(new IIndicator[] { _fastEma, _slowEma, _trendEma, _rsi, _macd, _parabolicSar }, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastEma);
			DrawIndicator(area, _slowEma);
			DrawIndicator(area, _trendEma);
			DrawIndicator(area, _rsi);
			DrawIndicator(area, _parabolicSar);
			DrawOwnTrades(area);
		}

		var macdArea = CreateChartArea();
		if (macdArea != null)
		{
			DrawIndicator(macdArea, _macd);
			DrawIndicator(macdArea, _macdBands);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var fastEmaVal = values[0];
		var slowEmaVal = values[1];
		var trendEmaVal = values[2];
		var rsiVal = values[3];
		var macdVal = values[4];
		var sarVal = values[5];

		if (fastEmaVal.IsEmpty || slowEmaVal.IsEmpty || trendEmaVal.IsEmpty ||
			rsiVal.IsEmpty || macdVal.IsEmpty || sarVal.IsEmpty)
			return;

		var fastEmaValue = fastEmaVal.ToDecimal();
		var slowEmaValue = slowEmaVal.ToDecimal();
		var trendEmaValue = trendEmaVal.ToDecimal();
		var rsiValue = rsiVal.ToDecimal();
		var macdLine = macdVal.ToDecimal();
		var sarValue = sarVal.ToDecimal();

		var macdSmaRaw = _macdSma.Process(macdLine, candle.OpenTime, true);
		if (macdSmaRaw.IsEmpty)
			return;
		var macdSmaValue = macdSmaRaw.ToDecimal();
		var macdMiddle = macdSmaValue;

		if (!_fastEma.IsFormed || !_slowEma.IsFormed || !_trendEma.IsFormed || !_macd.IsFormed || !_macdSma.IsFormed ||
				!_rsi.IsFormed || !_parabolicSar.IsFormed)
		{
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var priceStep = Security?.PriceStep ?? 0m;
		if (priceStep <= 0m)
			priceStep = 1m;
		var stopOffset = StopOffsetPoints * priceStep;

		if (Position > 0)
		{
			var updatedStop = CalculateStopPrice(true, sarValue, trendEmaValue, stopOffset);
			if (updatedStop.HasValue)
				_longStopPrice = updatedStop;
		}
		else if (Position < 0)
		{
			var updatedStop = CalculateStopPrice(false, sarValue, trendEmaValue, stopOffset);
			if (updatedStop.HasValue)
				_shortStopPrice = updatedStop;
		}

		ManageOpenPositions(candle, rsiValue, sarValue, priceStep);

		if (Position > 0)
			_waitingForLongEntry = false;
		if (Position < 0)
			_waitingForShortEntry = false;

		TryEnterLong(candle, fastEmaValue, slowEmaValue, trendEmaValue, rsiValue, macdLine, macdSmaValue, macdMiddle, sarValue, priceStep);
		TryEnterShort(candle, fastEmaValue, slowEmaValue, trendEmaValue, rsiValue, macdLine, macdSmaValue, macdMiddle, sarValue, priceStep);
	}

	private void ManageOpenPositions(ICandleMessage candle, decimal rsiValue, decimal sarValue, decimal priceStep)
	{
		var profitThreshold = MinimumProfitPoints * priceStep;

		if (Position > 0)
		{
			var volume = Math.Abs(Position);
			if (volume > 0m)
			{
				if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
				{
					SellMarket(volume);
					ResetLongState();
				}
				else
				{
					var entry = _longEntryPrice ?? candle.ClosePrice;
					_longEntryPrice ??= entry;

					var profit = candle.ClosePrice - entry;

					if ((profitThreshold <= 0m || profit >= profitThreshold) && !_longPartialExecuted)
					{
						if (UseRsiTakeProfit && rsiValue >= RsiTakeProfitLong)
						{
							CloseHalfLong(volume);
						}
						else if (UseSarTakeProfit && sarValue >= candle.ClosePrice)
						{
							CloseHalfLong(volume);
						}
					}

					if ((profitThreshold <= 0m || profit >= profitThreshold) && rsiValue <= RsiEntryLevel)
					{
						SellMarket(volume);
						ResetLongState();
					}
				}
			}
		}
		else
		{
			ResetLongState();
		}

		if (Position < 0)
		{
			var volume = Math.Abs(Position);
			if (volume > 0m)
			{
				if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
				{
					BuyMarket(volume);
					ResetShortState();
				}
				else
				{
					var entry = _shortEntryPrice ?? candle.ClosePrice;
					_shortEntryPrice ??= entry;

					var profit = entry - candle.ClosePrice;

					if ((profitThreshold <= 0m || profit >= profitThreshold) && !_shortPartialExecuted)
					{
						if (UseRsiTakeProfit && rsiValue <= RsiTakeProfitShort)
						{
							CloseHalfShort(volume);
						}
						else if (UseSarTakeProfit && sarValue <= candle.ClosePrice)
						{
							CloseHalfShort(volume);
						}
					}

					if ((profitThreshold <= 0m || profit >= profitThreshold) && rsiValue >= RsiEntryLevel)
					{
						BuyMarket(volume);
						ResetShortState();
					}
				}
			}
		}
		else
		{
			ResetShortState();
		}
	}

	private void CloseHalfLong(decimal volume)
	{
		var half = volume / 2m;
		if (half <= 0m)
			return;

		SellMarket(half);
		_longPartialExecuted = true;
	}

	private void CloseHalfShort(decimal volume)
	{
		var half = volume / 2m;
		if (half <= 0m)
			return;

		BuyMarket(half);
		_shortPartialExecuted = true;
	}

	private void TryEnterLong(ICandleMessage candle, decimal fastEma, decimal slowEma, decimal trendEma, decimal rsi, decimal macdLine,
		decimal macdSma, decimal macdMiddle, decimal sar, decimal priceStep)
	{
		if (Position > 0)
			return;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var upperShadow = Math.Abs(candle.HighPrice - candle.ClosePrice);
		var lowerShadow = Math.Abs(candle.OpenPrice - candle.LowPrice);

		var baseCondition = fastEma > slowEma && fastEma > trendEma && rsi > RsiEntryLevel &&
			macdLine > 0m;

		if (baseCondition)
		{
			var distanceLimit = DistanceThresholdPoints * priceStep;
			var distance = candle.ClosePrice - trendEma;

			if (distanceLimit > 0m && distance >= distanceLimit)
			{
				_waitingForLongEntry = true;
			}
			else
			{
				OpenLong(candle, sar, trendEma, priceStep);
			}
		}
		else if (_waitingForLongEntry && candle.LowPrice <= fastEma && rsi < RsiTakeProfitLong && sar < candle.ClosePrice)
		{
			OpenLong(candle, sar, trendEma, priceStep);
			_waitingForLongEntry = false;
		}
	}

	private void TryEnterShort(ICandleMessage candle, decimal fastEma, decimal slowEma, decimal trendEma, decimal rsi, decimal macdLine,
		decimal macdSma, decimal macdMiddle, decimal sar, decimal priceStep)
	{
		if (Position < 0)
			return;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var upperShadow = Math.Abs(candle.HighPrice - candle.ClosePrice);
		var lowerShadow = Math.Abs(candle.OpenPrice - candle.LowPrice);

		var baseCondition = fastEma < slowEma && fastEma < trendEma && rsi < RsiEntryLevel &&
			macdLine < 0m;

		if (baseCondition)
		{
			var distanceLimit = DistanceThresholdPoints * priceStep;
			var distance = trendEma - candle.ClosePrice;

			if (distanceLimit > 0m && distance >= distanceLimit)
			{
				_waitingForShortEntry = true;
			}
			else
			{
				OpenShort(candle, sar, trendEma, priceStep);
			}
		}
		else if (_waitingForShortEntry && candle.HighPrice >= fastEma && rsi > RsiTakeProfitShort && sar > candle.ClosePrice)
		{
			OpenShort(candle, sar, trendEma, priceStep);
			_waitingForShortEntry = false;
		}
	}

	private void OpenLong(ICandleMessage candle, decimal sar, decimal trendEma, decimal priceStep)
	{
		var volume = TradeVolume;
		if (volume <= 0m)
			return;

		if (Position < 0m)
			BuyMarket(Math.Abs(Position));

		BuyMarket(volume);

		_longEntryPrice = candle.ClosePrice;
		_longPartialExecuted = false;

		_longStopPrice = CalculateStopPrice(true, sar, trendEma, StopOffsetPoints * priceStep);
	}

	private void OpenShort(ICandleMessage candle, decimal sar, decimal trendEma, decimal priceStep)
	{
		var volume = TradeVolume;
		if (volume <= 0m)
			return;

		if (Position > 0m)
			SellMarket(Math.Abs(Position));

		SellMarket(volume);

		_shortEntryPrice = candle.ClosePrice;
		_shortPartialExecuted = false;

		_shortStopPrice = CalculateStopPrice(false, sar, trendEma, StopOffsetPoints * priceStep);
	}

	private decimal? CalculateStopPrice(bool isLong, decimal sar, decimal trendEma, decimal offset)
	{
		decimal? stop = null;

		if (UseSarStopLoss)
			stop = isLong ? sar - offset : sar + offset;

		if (UseTrendStopLoss)
			stop = isLong ? trendEma - offset : trendEma + offset;

		return stop;
	}

	private void ResetLongState()
	{
		_longPartialExecuted = false;
		_longStopPrice = null;
		_longEntryPrice = null;
	}

	private void ResetShortState()
	{
		_shortPartialExecuted = false;
		_shortStopPrice = null;
		_shortEntryPrice = null;
	}
}