Открыть на GitHub

Стратегия Natuseko Protrader 4H

Обзор

Natuseko Protrader 4H — порт эксперта MetaTrader 4 NatusekoProtrader4HStrategy на платформу StockSharp. Алгоритм комбинирует три EMA, осциллятор MACD, фильтрованный полосами Боллинджера, уровни RSI и Parabolic SAR, чтобы находить импульсные свечи на таймфрейме четыре часа. Перенос воспроизводит логику отложенного входа после сильной свечи и блочную систему частичного закрытия позиций.

Логика торговли

  1. Подписка на свечи типа CandleType (по умолчанию 4H) и обработка только завершённых свечей.
  2. Расчёт трёх EMA по ценам закрытия: быстрой, медленной и трендовой.
  3. Построение MACD с параметрами оригинала, сглаживание главной линии SMA и наложение полос Боллинджера на значения MACD.
  4. Расчёт RSI и Parabolic SAR; именно они задают условия входа и выхода.
  5. Модель ищет бычью свечу, когда:
    • быстрая EMA выше медленной и трендовой;
    • RSI выше RsiEntryLevel, но ниже RsiTakeProfitLong;
    • главная линия MACD и её SMA расположены выше средней полосы Боллинджера;
    • тело свечи больше обоих хвостов (сильное закрытие в сторону тренда);
    • Parabolic SAR под ценой закрытия.
  6. Медвежий сигнал зеркальный: порядок EMA обратный, RSI между RsiTakeProfitShort и RsiEntryLevel, MACD ниже средней полосы, медвежье тело и SAR над ценой.
  7. Если свеча находится слишком далеко от трендовой EMA (DistanceThresholdPoints), стратегия фиксирует режим ожидания и ждёт возврата к быстрой EMA. Когда цена касается EMA и фильтры по RSI и SAR остаются в силе, выполняется отложенный вход (для шорта условия аналогичны).
  8. При готовности стратегия закрывает противоположную позицию и открывает новую сделку объёмом TradeVolume. Стоп-лосс определяется SAR, если активирован UseSarStopLoss, иначе — трендовой EMA. Смещение StopOffsetPoints переводится в цену через шаг цены инструмента.
  9. Управление открытым лонгом:
    • при пробое стоп-уровня позиция закрывается полностью;
    • после накопления прибыли не меньше MinimumProfitPoints возможен один частичный выход (50 %) по RSI (RsiTakeProfitLong) или по развороту SAR над ценой;
    • когда прибыль достаточна и RSI падает ниже RsiEntryLevel, оставшийся объём закрывается.
  10. Для шортов используются симметричные правила с обратными порогами RSI и SAR.

Управление позицией

  • Частичное закрытие выполняется не более одного раза на сторону. После него стратегия ждёт условия полного выхода или срабатывания стопа.
  • Стоп-цены пересчитываются на каждой свече по свежим значениям SAR/EMA, что соответствует логике MQL.
  • При нулевой позиции внутренние флаги ожидания, ссылки на стопы и признак частичного выхода сбрасываются.

Параметры

Имя Тип Значение по умолчанию Описание
CandleType DataType 4H Основной таймфрейм.
TradeVolume decimal 0.1 Объём сделки.
FastEmaPeriod int 13 Период быстрой EMA.
SlowEmaPeriod int 21 Период медленной EMA.
TrendEmaPeriod int 55 Трендовая EMA, используемая также для стопа и фильтра расстояния.
MacdFastPeriod int 5 Быстрый период MACD.
MacdSlowPeriod int 200 Медленный период MACD.
MacdSignalPeriod int 1 Период сигнальной линии MACD.
BollingerPeriod int 20 Длина выборки MACD для полос Боллинджера.
BollingerWidth decimal 1 Множитель стандартного отклонения полос Боллинджера.
MacdSmaPeriod int 3 Период SMA для сглаживания линии MACD.
RsiPeriod int 21 Период RSI.
RsiEntryLevel decimal 50 Центральный порог RSI.
RsiTakeProfitLong decimal 65 RSI для частичного выхода из лонга.
RsiTakeProfitShort decimal 35 RSI для частичного выхода из шорта.
DistanceThresholdPoints decimal 100 Максимально допустимое расстояние (в пунктах) от цены до трендовой EMA.
SarStep decimal 0.02 Шаг ускорения Parabolic SAR.
SarMaximum decimal 0.2 Максимальное ускорение Parabolic SAR.
UseSarStopLoss bool false Использовать ли SAR для расчёта стоп-лосса.
UseTrendStopLoss bool true Использовать ли трендовую EMA в качестве стопа.
StopOffsetPoints int 0 Дополнительное смещение стопа в пунктах.
UseSarTakeProfit bool true Включить частичный выход по сигналу SAR.
UseRsiTakeProfit bool true Включить частичный выход по RSI.
MinimumProfitPoints decimal 5 Минимальная прибыль (в пунктах) для активации правил фиксации прибыли.

Отличия от оригинала

  • В StockSharp используется нетто-позиция, поэтому перед разворотом стратегия закрывает текущее направление, имитируя MT4 с одним ордером.
  • Управление рисками реализовано рыночными заявками, так как в StockSharp нет отдельного стопа на каждый ордер. Поведение (один частичный и окончательный выход) остаётся прежним.
  • Расстояния и прибыли рассчитываются через PriceStep. Если у инструмента шаг цены не задан, используется значение 1 — в этом случае стоит скорректировать параметры под конкретный рынок.

Рекомендации по использованию

  • Подберите TradeVolume под спецификацию инструмента; конструктор дополнительно присваивает это значение свойству Strategy.Volume.
  • Если сигналов слишком мало из-за большого отрыва цены от трендовой EMA, уменьшите DistanceThresholdPoints или обнулите его.
  • Используйте график: стратегия рисует свечи, все три EMA, RSI, Parabolic SAR и MACD с полосами Боллинджера, что облегчает контроль переноса.
  • Параметры MACD (5/200/1) полностью повторяют MQL-версию и дают очень сглаженный сигнал — перед реальной торговлей стоит провести оптимизацию.
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;
	}
}