Auf GitHub ansehen

Natuseko Protrader 4H-Strategie

Überblick

Die Natuseko Protrader 4H-Strategie ist eine StockSharp-Portierung des MetaTrader 4-Expertenberaters NatusekoProtrader4HStrategy. Das Original Der Roboter kombiniert exponentielle gleitende Durchschnitte, einen durch Bollinger-Bänder gefilterten MACD-Oszillator, RSI-Schwellenwerte und den Parabolic SAR Identifizieren Sie starke Ausbruchskerzen im Vier-Stunden-Zeitrahmen. Wenn eine qualifizierte Kerze erscheint, öffnet sich das System entweder sofort oder wartet vor dem Eintritt auf einen Rückzug in Richtung des schnellen EMA. Sobald die Strategie positioniert ist, führt sie teilweise Gewinnmitnahmen und vollständige Ausstiege durch basiert auf den Signalen RSI und Parabolic SAR und repliziert den im MQL-Code vorhandenen Geldverwaltungsblock.

Handelslogik

  1. Abonnieren Sie den durch CandleType definierten primären Kerzenstream (standardmäßig 4-Stunden-Kerzen) und verarbeiten Sie nur fertige Kerzen.
  2. Berechnen Sie drei exponentielle gleitende Durchschnitte (schnell, langsam und Trend) für die Schlusskurse. Alle drei haben konfigurierbare Längen.
  3. Geben Sie den Indikator MACD ein (schnelle, langsame und Signalperioden aus EA) und wenden Sie einen einfachen gleitenden Durchschnitt plus Bollinger-Bänder an die Hauptzeile MACD. Die Mittellinie Bollinger fungiert als Referenzniveau, das von der Version MQL verwendet wird.
  4. Berechnen Sie den RSI für Schlusskurse und den Parabolic SAR unter Verwendung vollständiger Kerzendaten. Diese Indikatoren steuern sowohl Ein- als auch Ausstiege.
  5. Erkennen Sie bullische Setup-Kerzen, wenn alle der folgenden Bedingungen erfüllt sind:
    • Der Wert „Schnell“ EMA liegt sowohl über dem Wert „Langsam“ als auch über dem Wert „Trend“ EMA.
    • RSI liegt über RsiEntryLevel, aber unter RsiTakeProfitLong.
    • Die Hauptlinie MACD liegt sowohl über der kurzen SMA als auch über der Mittellinie Bollinger. der SMA liegt ebenfalls über der Mittellinie.
    • Der Kerzenkörper ist größer als beide Schatten, was bedeutet, dass die Kerze in Bewegungsrichtung stark schließt.
    • Parabolic SAR liegt unterhalb des Kerzenschlusses.
  6. Erkennen Sie bärische Setups mithilfe der symmetrischen Prüfungen (schneller EMA unten, RSI zwischen RsiTakeProfitShort und RsiEntryLevel, MACD-Werte unterhalb der Bollinger-Mittellinie, bärischer Kerzenkörper und SAR über dem Schlusskurs).
  7. Wenn die qualifizierte Kerze zu weit vom Trend EMA entfernt ist (Entfernung über DistanceThresholdPoints), setzen Sie eine ausstehende Markierung und warten Sie auf eine Rückzug. Ein Long-Einstieg wird ausgelöst, sobald der Preis den schnellen EMA berührt, während RSI und SAR weiterhin dem bullischen Szenario entsprechen; die Der kurze Einstieg funktioniert analog bei Pullbacks zum schnellen EMA von unten.
  8. Wenn kein Pullback erforderlich ist, schließt die Strategie alle entgegengesetzten Positionen und eröffnet eine neue Position mit TradeVolume Lots. Stop-Loss Die Platzierung folgt den EA-Regeln: Zuerst wird dem Parabolic SAR der Vorzug gegeben, wenn UseSarStopLoss aktiviert ist, andernfalls der Trend EMA wird verwendet. StopOffsetPoints wird mit der Preisstufe des Instruments in einen Preisabstand umgewandelt und auf das Stop-Level angewendet.
  9. Während eine Long-Position offen ist, berechnet die Strategie den Stop-Preis kontinuierlich neu und verwaltet Ausstiege:
    • Wenn der Preis unter den Stop fällt, wird die gesamte Position geschlossen.
    • Nach Erreichen von mindestens MinimumProfitPoints Gewinn (in Instrumentenpunkten) kann die Strategie die Hälfte der Position schließen, wenn der RSI überschreitet RsiTakeProfitLong oder wenn der Parabolic SAR über dem Preis liegt (gesteuert durch UseRsiTakeProfit und UseSarTakeProfit).
    • Sobald der Gewinn ausreichend ist und RSI wieder unter RsiEntryLevel fällt, wird das verbleibende Long-Engagement geschlossen.
  10. Short-Positionen spiegeln die gleichen Regeln wider, wobei die RSI-Schwellenwerte umgekehrt und SAR-Schecks im Verhältnis zum Preis umgedreht sind.

Positionsmanagement

  • Teilausstiege kommen höchstens einmal pro Handelsseite vor. Nach dem Schließen der Hälfte der Position wartet die Strategie auf die vollständige Ausstiegsbedingung (RSI überschreitet das neutrale Niveau oder ein Stop-Loss-Treffer).
  • Stop-Loss-Preise werden bei jeder Kerze unter Verwendung des neuesten Parabolic SAR- oder Trendwerts EMA neu berechnet, um mit der MQL-Logik in Einklang zu bleiben.
  • Wenn die Positionsgröße auf Null zurückkehrt, wird der interne Status (Pending-Entry-Flags, Stop-Referenzen und Teil-Exit-Marker) zurückgesetzt, sodass die Positionsgröße auf Null zurückkehrt Der nächste Trade beginnt sauber.

Parameter

Name Typ Standard Beschreibung
CandleType DataType Zeitrahmen von 4 Stunden Primärer Zeitrahmen, der von der Strategie verarbeitet wird.
TradeVolume decimal 0.1 Auftragsvolumen, das für Einträge verwendet wird.
FastEmaPeriod int 13 Länge des schnellen EMA-Filters.
SlowEmaPeriod int 21 Länge des langsameren EMA-Filters.
TrendEmaPeriod int 55 EMA wird für Distanzprüfungen und Stop-Loss-Platzierung verwendet.
MacdFastPeriod int 5 Schnelle Länge von EMA innerhalb des Indikators MACD.
MacdSlowPeriod int 200 Langsame Länge von EMA innerhalb des Indikators MACD.
MacdSignalPeriod int 1 Länge des gleitenden Signaldurchschnitts innerhalb des Indikators MACD.
BollingerPeriod int 20 Anzahl der MACD Stichproben, die zur Berechnung von Bollinger Bändern verwendet werden.
BollingerWidth decimal 1 Standardabweichungsmultiplikator für die MACD Bollinger-Bänder.
MacdSmaPeriod int 3 Länge der MACD Glättung SMA.
RsiPeriod int 21 Länge des RSI-Indikators.
RsiEntryLevel decimal 50 Neutraler RSI-Schwellenwert, der von Ein- und Austrittsregeln gemeinsam genutzt wird.
RsiTakeProfitLong decimal 65 RSI-Level, das teilweise Gewinnmitnahmen für Long-Positionen ermöglicht.
RsiTakeProfitShort decimal 35 RSI-Level, das teilweise Gewinnmitnahmen für Short-Positionen ermöglicht.
DistanceThresholdPoints decimal 100 Maximaler Abstand in Instrumentenpunkten zwischen Preis und Trend EMA, bevor der Einstieg verzögert wird.
SarStep decimal 0.02 Beschleunigungsschritt für den Parabolic SAR.
SarMaximum decimal 0.2 Maximale Beschleunigung für den Parabolic SAR.
UseSarStopLoss bool false Verwenden Sie Parabolic SAR, um den Schutzstopp abzuleiten.
UseTrendStopLoss bool true Nutzen Sie den Trend EMA, um den Schutzstopp abzuleiten.
StopOffsetPoints int 0 Zusätzlicher Offset (in Punkten) zum Schutz-Stopp-Preis.
UseSarTakeProfit bool true Teilausstiege aktivieren, wenn der Preis den Parabolic SAR überschreitet.
UseRsiTakeProfit bool true Aktivieren Sie Teilexits, wenn RSI den Take-Profit-Schwellenwert erreicht.
MinimumProfitPoints decimal 5 Mindestgewinn (in Punkten), bevor die Regeln für teilweise oder vollständige Gewinnmitnahmen aktiviert werden.

Unterschiede zum Original EA

  • StockSharp handelt Nettopositionen. Um das Single-Ticket-Verhalten von MetaTrader zu emulieren, schließt die Strategie automatisch das Gegenteil aus Exposition bevor Sie einen neuen Trade in die andere Richtung eröffnen.
  • Money-Management-Helfer werden mit Marktaufträgen implementiert, anstatt einzelne Aufträge zu ändern, da StockSharp nicht verwaltet wird Haltestellen pro Ticket. Der Effekt entspricht dem von EA: ein teilweiser Ausstieg, gefolgt von einem endgültigen Ausstieg, wenn die Dynamik von RSI nachlässt.
  • Preisentfernungsberechnungen basieren auf dem Instrument PriceStep. Wenn das Wertpapier keinen Preisschritt definiert, geht die Strategie von einem aus Schritt 1. Passen Sie DistanceThresholdPoints und MinimumProfitPoints entsprechend für Instrumente an, die unterschiedliche Punktgrößen verwenden.

Anwendungstipps

  • Konfigurieren Sie TradeVolume entsprechend der Chargenstufe des Instruments; Der Konstruktor weist also auch Strategy.Volume denselben Wert zu Hilfsmethoden verwenden die erwartete Größe.
  • Wenn Trades zu oft verzögert werden, weil Kerzen weit vom Trend EMA schließen, senken Sie DistanceThresholdPoints oder deaktivieren Sie den Filter um auf Null setzen.
  • Es wird empfohlen, die Strategie zu zeichnen: Der Code zeichnet Kerzen, die drei EMAs, RSI, Parabolic SAR und MACD Bollinger Bänder, damit Sie es können Bestätigen Sie die konvertierte Logik visuell.
  • Die MACD-Parameter spiegeln die ungewöhnliche Kombination von EA wider (schnell=5, langsam=200, Signal=1). Erwägen Sie eine Optimierung vor der Live-Schaltung weil eine so lange langsame Periode sehr glatte, aber nacheilende Werte erzeugt.
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;
	}
}