Auf GitHub ansehen

Tägliche Trendumkehr

Überblick

Daily Trend Reversal ist eine Portierung des MetaTrader 4 Expert Advisors dailyTrendReversal_D1. Die Strategie verankert Intraday-Trades an den Eröffnungs-, Höchst- und Tiefstständen des aktuellen Tages und beteiligt sich nur, wenn sowohl die Preisbewegung als auch der Commodity Channel Index (CCI) die gleiche Richtungsneigung bestätigen. Der Handel ist auf eine konfigurierbare GMT-Sitzung beschränkt, wird optional nach Erreichen eines täglichen Gewinnziels angehalten und kann Positionen sofort verlassen, wenn die Filter auf die entgegengesetzte Seite wechseln.

Strategielogik

Tägliche Bias-Filter

  • Richtungsschritte – Die Strategie bewertet bis zu drei Bedingungen, um die tägliche Tendenz zu validieren:
    1. Der Abstand vom aktuellen Preis zum Tagesextrem muss einen in Pips ausgedrückten Risikoschwellenwert überschreiten.
    2. Der Abstand vom Eröffnungskurs zum entgegengesetzten Extrem muss ebenfalls die Risikoschwelle überschreiten und der Preis muss innerhalb von 10 Pips vom täglichen Eröffnungskurs bleiben.
    3. (Optional) Die aktuelle Kerze muss in der Bewegungsrichtung schließen, während der Preis noch innerhalb von 10 Pips der täglichen Eröffnung liegt.
  • Range-Dominanz – vergleicht den Abstand von der Eröffnung zum Hoch mit der Distanz von der Eröffnung zum Tief. Die längere Seite definiert den aktiven Trend.
  • CCI-Trend – die letzten drei abgeschlossenen CCI-Werte müssen monoton steigend (für Long-Positionen) oder fallend (für Short-Positionen) sein.

Einreisebestimmungen

  • Lange Einträge
    • Nur während des konfigurierten GMT-Handelsfensters an Werktagen zulässig.
    • Der aktuelle Preis muss über der täglichen Eröffnung liegen, die Richtungsschritte müssen einen Aufwärtstrend bestätigen, die Range-Dominanz muss den Aufwärtstrend begünstigen und der CCI-Trend muss steigend sein.
    • Eröffnet eine Long-Position nur, wenn die Nettoposition flach oder short ist (Short-Engagement wird im Rahmen der Umkehrung zu Long geschlossen).
  • Kurze Einträge
    • Gespiegelte Bedingungen: Preis unter der täglichen Eröffnung, Richtungsschritte bestätigen einen Abwärtstrend, Range-Dominanz begünstigt die Abwärtsbewegung und der CCI-Trend ist rückläufig.
    • Wird nur geöffnet, wenn die Nettoposition flach oder lang ist.

Ausgangsregeln

  • Fester Take-Profit/Stop-Loss – ausgedrückt in Pips relativ zum Einstieg. Ein Wert von 0 deaktiviert die entsprechende Ebene.
  • Sitzungs- und Haltekontrolle – Sobald die GMT-Schlussstunde erreicht ist oder die Haltezeit in Stunden abgelaufen ist, werden profitable Positionen sofort geschlossen. Verlierergeschäfte wechseln in den Break-Even-Modus und werden geschlossen, sobald der Preis zum Einstiegsniveau zurückkehrt.
  • Umkehrausgang (optional) – wenn aktiviert, werden Long-Positionen geschlossen, wenn die Abwärtsfilter übereinstimmen (Preis unter dem Eröffnungskurs und Tages-/CCI-Trends, die nach unten zeigen); Kurzschlüsse werden symmetrisch geschlossen, wenn die nach oben gerichteten Filter ausgerichtet sind.
  • Täglicher Gewinnstopp – kombiniert den realisierten Gewinn seit dem ersten Handel des Tages mit dem variablen PnL. Wenn der konfigurierte Schwellenwert erreicht ist, werden alle Positionen geschlossen und neue Eingaben ausgesetzt, bis der Parameter manuell wieder aktiviert wird.

Parameter

  • Auto Trading – schaltet um, ob die Strategie neue Trades eröffnen darf.
  • Reversal Exit – ermöglicht sofortige Ausstiege, wenn der entgegengesetzte Tagestrend bestätigt wird.
  • Trendschritte – wählt aus, wie viele Schrittfilter (1–3) durchlaufen müssen, um die tägliche Tendenz zu validieren.
  • Volumen – Auftragsvolumen für Markteintritte.
  • Take Profit (Pips) – feste Gewinnzielentfernung; zum Deaktivieren auf 0 setzen.
  • Stop-Loss (Pips) – Schutzstopp-Distanz; zum Deaktivieren auf 0 setzen.
  • Profit Stop – Gewinnziel in Preiseinheiten, das den Handel für den Rest des Tages unterbricht; 0 deaktiviert die Funktion.
  • GMT Diff – Kartenzeit minus GMT (in Stunden). Wird verwendet, um GMT-Sitzungsgrenzen in Diagrammzeit umzuwandeln.
  • Startstunde / Endstunde – GMT-Stunden, die das Handelsfenster für neue Positionen begrenzen.
  • Abschlussstunde – GMT-Stunde, nach der die Strategie Ausstiege erzwingt oder die Break-Even-Logik aktiviert.
  • Haltezeiten – maximale Zeitspanne, die ein Trade offen bleiben darf, bevor die Sitzungslogik ausgelöst wird.
  • Risiko (Pips) – Pip-Abstand, der von den Richtungsschritten verwendet wird.
  • CCI Zeitraum – Anzahl der Perioden für den Commodity Channel Index.
  • Kerzentyp – Zeitrahmen, der die Berechnungen steuert (Standard: 15-Minuten-Kerzen).

Zusätzliche Hinweise

  • Die Strategie erkennt die Pip-Größe anhand der Preisstufe des Wertpapiers. Fünf- und dreistellige FX-Symbole wandeln die konfigurierten Pip-Abstände automatisch in Preisinkremente um.
  • Die tägliche Gewinnverfolgung wird mit der ersten Kerze jedes neuen Handelstages zurückgesetzt, indem der aktuell realisierte PnL als neue Basislinie erfasst wird.
  • Für diese Strategie gibt es keine Python-Implementierung; Im Paket API ist nur die C#-Version enthalten.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader strategy dailyTrendReversal_D1.
/// Combines daily open/high/low levels with a multi-step filter and CCI trend confirmation.
/// Applies strict session control, optional reversal exits, and a configurable daily profit stop.
/// </summary>
public class DailyTrendReversalStrategy : Strategy
{
	private readonly StrategyParam<bool> _enableAutoTrading;
	private readonly StrategyParam<bool> _enableReversal;
	private readonly StrategyParam<int> _trendSteps;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _profitStop;
	private readonly StrategyParam<int> _gmtDiff;
	private readonly StrategyParam<int> _gmtStartHour;
	private readonly StrategyParam<int> _gmtEndHour;
	private readonly StrategyParam<int> _gmtClosingHour;
	private readonly StrategyParam<int> _holdingHours;
	private readonly StrategyParam<int> _riskPips;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private CommodityChannelIndex _cci = null!;

	private readonly List<decimal> _cciHistory = new(3);

	private decimal _pipSize;
	private decimal _tenPips;

	private DateTime? _currentDay;
	private decimal _dailyOpen;
	private decimal _dailyHigh;
	private decimal _dailyLow;
	private decimal _dayPnLBase;

	private bool _tradingSuspended;

	private decimal _lastClose;
	private DateTimeOffset _lastCandleTime;

	private decimal _longEntryPrice;
	private DateTimeOffset? _longEntryTime;
	private decimal _longTakeProfitPrice;
	private decimal _longStopPrice;
	private bool _longBreakEvenActive;

	private decimal _shortEntryPrice;
	private DateTimeOffset? _shortEntryTime;
	private decimal _shortTakeProfitPrice;
	private decimal _shortStopPrice;
	private bool _shortBreakEvenActive;

	private DateTimeOffset _lastEntryTime;

	private enum TrendDirections
	{
		Flat,
		Up,
		Down,
	}

	/// <summary>
	/// Enables automated entries within the trading window.
	/// </summary>
	public bool EnableAutoTrading
	{
		get => _enableAutoTrading.Value;
		set => _enableAutoTrading.Value = value;
	}

	/// <summary>
	/// Enables closing positions when the opposite trend is confirmed.
	/// </summary>
	public bool EnableReversal
	{
		get => _enableReversal.Value;
		set => _enableReversal.Value = value;
	}

	/// <summary>
	/// Number of step filters applied to the daily trend evaluation.
	/// </summary>
	public int TrendSteps
	{
		get => _trendSteps.Value;
		set => _trendSteps.Value = value;
	}


	/// <summary>
	/// Take-profit distance expressed in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Daily profit target that halts trading when reached (includes floating PnL).
	/// </summary>
	public decimal ProfitStop
	{
		get => _profitStop.Value;
		set => _profitStop.Value = value;
	}

	/// <summary>
	/// Difference between chart time and GMT in hours.
	/// </summary>
	public int GmtDiff
	{
		get => _gmtDiff.Value;
		set => _gmtDiff.Value = value;
	}

	/// <summary>
	/// GMT start hour of the active trading window.
	/// </summary>
	public int GmtStartHour
	{
		get => _gmtStartHour.Value;
		set => _gmtStartHour.Value = value;
	}

	/// <summary>
	/// GMT end hour for accepting new entries.
	/// </summary>
	public int GmtEndHour
	{
		get => _gmtEndHour.Value;
		set => _gmtEndHour.Value = value;
	}

	/// <summary>
	/// GMT hour when all trades should be protected or closed.
	/// </summary>
	public int GmtClosingHour
	{
		get => _gmtClosingHour.Value;
		set => _gmtClosingHour.Value = value;
	}

	/// <summary>
	/// Maximum holding time in hours before forcing exits.
	/// </summary>
	public int HoldingHours
	{
		get => _holdingHours.Value;
		set => _holdingHours.Value = value;
	}

	/// <summary>
	/// Risk threshold in pips used by the trend step filter.
	/// </summary>
	public int RiskPips
	{
		get => _riskPips.Value;
		set => _riskPips.Value = value;
	}

	/// <summary>
	/// Length of the Commodity Channel Index indicator.
	/// </summary>
	public int CciPeriod
	{
		get => _cciPeriod.Value;
		set => _cciPeriod.Value = value;
	}

	/// <summary>
	/// Candle type that drives the strategy calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="DailyTrendReversalStrategy"/> class.
	/// </summary>
	public DailyTrendReversalStrategy()
	{
		_enableAutoTrading = Param(nameof(EnableAutoTrading), true)
		.SetDisplay("Auto Trading", "Enable automated entries inside the session", "Trading");

		_enableReversal = Param(nameof(EnableReversal), true)
		.SetDisplay("Reversal Exit", "Close positions on confirmed opposite trend", "Trading");

		_trendSteps = Param(nameof(TrendSteps), 3)
		.SetRange(0, 3)
		.SetDisplay("Trend Steps", "Number of filters used for daily direction", "Trend Filter");


		_takeProfitPips = Param(nameof(TakeProfitPips), 30m)
		.SetRange(0m, 1000m)
		.SetDisplay("Take Profit (pips)", "Distance to fixed take profit (0 disables)", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 0m)
		.SetRange(0m, 1000m)
		.SetDisplay("Stop Loss (pips)", "Distance to protective stop loss (0 disables)", "Risk");

		_profitStop = Param(nameof(ProfitStop), 100m)
		.SetRange(0m, 100000m)
		.SetDisplay("Profit Stop", "Daily profit target that pauses trading", "Risk");

		_gmtDiff = Param(nameof(GmtDiff), 0)
		.SetDisplay("GMT Diff", "Chart time minus GMT in hours", "Session");

		_gmtStartHour = Param(nameof(GmtStartHour), 5)
		.SetRange(0, 23)
		.SetDisplay("Start Hour", "Session start hour in GMT", "Session");

		_gmtEndHour = Param(nameof(GmtEndHour), 14)
		.SetRange(0, 23)
		.SetDisplay("End Hour", "Session end hour for new trades (GMT)", "Session");

		_gmtClosingHour = Param(nameof(GmtClosingHour), 18)
		.SetRange(0, 23)
		.SetDisplay("Closing Hour", "Session close hour for active trades (GMT)", "Session");

		_holdingHours = Param(nameof(HoldingHours), 10)
		.SetRange(0, 48)
		.SetDisplay("Holding Hours", "Maximum holding time for positions", "Risk");

		_riskPips = Param(nameof(RiskPips), 30)
		.SetRange(0, 1000)
		.SetDisplay("Risk (pips)", "Risk filter threshold used by trend steps", "Trend Filter");

		_cciPeriod = Param(nameof(CciPeriod), 15)
		.SetGreaterThanZero()
		.SetDisplay("CCI Period", "Length of the Commodity Channel Index", "Trend Filter");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Primary timeframe for calculations", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

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

		_cciHistory.Clear();
		_pipSize = 0m;
		_tenPips = 0m;
		_currentDay = null;
		_dailyOpen = 0m;
		_dailyHigh = 0m;
		_dailyLow = 0m;
		_dayPnLBase = 0m;
		_tradingSuspended = false;
		_lastClose = 0m;
		_lastCandleTime = default;

		_longEntryPrice = 0m;
		_longEntryTime = null;
		_longTakeProfitPrice = 0m;
		_longStopPrice = 0m;
		_longBreakEvenActive = false;

		_shortEntryPrice = 0m;
		_shortEntryTime = null;
		_shortTakeProfitPrice = 0m;
		_shortStopPrice = 0m;
		_shortBreakEvenActive = false;

		_lastEntryTime = default;
	}

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

		_pipSize = GetPipSize();
		_tenPips = 10m * _pipSize;
		_dayPnLBase = PnL;
		_tradingSuspended = false;

		_cci = new CommodityChannelIndex
		{
			Length = CciPeriod,
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_cci, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _cci);
			DrawOwnTrades(area);
		}

		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle, decimal cciValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		_lastCandleTime = candle.CloseTime;
		_lastClose = candle.ClosePrice;

		UpdateDailyLevels(candle);
		UpdateCciHistory(cciValue);

		var riskDistance = Math.Max(0, RiskPips) * _pipSize;
		var trend = GetDirectionalTrend(candle, riskDistance);
		var rangeTrend = GetRangeTrend();
		var cciTrend = GetCciTrend();

		ManageExistingPositions(candle, rangeTrend, cciTrend, riskDistance);
		HandleProfitStop();

		if (!CanOpenPositions(candle))
		return;

		if (!_cci.IsFormed)
		return;

		EvaluateEntries(candle, trend, rangeTrend, cciTrend);
	}

	private void EvaluateEntries(ICandleMessage candle, TrendDirections trend, TrendDirections rangeTrend, TrendDirections cciTrend)
	{
		if (_lastEntryTime != default && candle.CloseTime - _lastEntryTime < TimeSpan.FromHours(3))
			return;

		var price = candle.ClosePrice;

		if (trend == TrendDirections.Up && rangeTrend == TrendDirections.Up && cciTrend == TrendDirections.Up && price > _dailyOpen && Position <= 0m)
		{
			var volume = Volume + Math.Max(0m, -Position);
			if (volume > 0m)
			{
				BuyMarket(volume);
				_lastEntryTime = candle.CloseTime;
				LogInfo($"Enter long at {price} due to daily trend confirmation.");
			}
		}

		if (trend == TrendDirections.Down && rangeTrend == TrendDirections.Down && cciTrend == TrendDirections.Down && price < _dailyOpen && Position >= 0m)
		{
			var volume = Volume + Math.Max(0m, Position);
			if (volume > 0m)
			{
				SellMarket(volume);
				_lastEntryTime = candle.CloseTime;
				LogInfo($"Enter short at {price} due to daily trend confirmation.");
			}
		}
	}

	private void ManageExistingPositions(ICandleMessage candle, TrendDirections rangeTrend, TrendDirections cciTrend, decimal riskDistance)
	{
		if (Position > 0m)
		{
			ManageLongPosition(candle, rangeTrend, cciTrend, riskDistance);
		}
		else if (Position < 0m)
		{
			ManageShortPosition(candle, rangeTrend, cciTrend, riskDistance);
		}
	}

	private void ManageLongPosition(ICandleMessage candle, TrendDirections rangeTrend, TrendDirections cciTrend, decimal riskDistance)
	{
		var price = candle.ClosePrice;

		if (_longTakeProfitPrice > 0m && price >= _longTakeProfitPrice)
		{
			SellMarket(Position);
			LogInfo("Long take profit reached.");
			return;
		}

		if (_longStopPrice > 0m && price <= _longStopPrice)
		{
			SellMarket(Position);
			LogInfo("Long stop loss triggered.");
			return;
		}

		var holdingExceeded = HoldingHours > 0 && _longEntryTime is DateTimeOffset longEntry && candle.CloseTime - longEntry >= TimeSpan.FromHours(HoldingHours);
		var closingHourReached = GmtClosingHour > 0 && IsAfterOrEqualHour(candle.CloseTime, GmtClosingHour + GmtDiff);

		if ((holdingExceeded || closingHourReached) && _longEntryPrice != 0m)
		{
			if (price > _longEntryPrice)
			{
				SellMarket(Position);
				LogInfo("Long closed with profit due to session or holding limit.");
				return;
			}

			if (!_longBreakEvenActive)
			{
				_longBreakEvenActive = true;
				LogInfo("Long switched to break-even mode due to session or holding limit.");
			}
		}

		if (_longBreakEvenActive && _longEntryPrice != 0m && price >= _longEntryPrice)
		{
			SellMarket(Position);
			_longBreakEvenActive = false;
			LogInfo("Long closed at break-even after session limit.");
			return;
		}

		if (EnableReversal && _dailyOpen != 0m)
		{
			var step1 = TrendSteps >= 0 && price - _dailyLow > riskDistance;
			var step2 = TrendSteps >= 2 && _dailyHigh - _dailyOpen >= riskDistance && _dailyOpen - price <= _tenPips;

			if (price < _dailyOpen && (step1 || step2) && rangeTrend == TrendDirections.Down && cciTrend == TrendDirections.Down)
			{
				SellMarket(Position);
				LogInfo("Long reversed due to opposite trend confirmation.");
			}
		}
	}

	private void ManageShortPosition(ICandleMessage candle, TrendDirections rangeTrend, TrendDirections cciTrend, decimal riskDistance)
	{
		var price = candle.ClosePrice;

		if (_shortTakeProfitPrice > 0m && price <= _shortTakeProfitPrice)
		{
			BuyMarket(Math.Abs(Position));
			LogInfo("Short take profit reached.");
			return;
		}

		if (_shortStopPrice > 0m && price >= _shortStopPrice)
		{
			BuyMarket(Math.Abs(Position));
			LogInfo("Short stop loss triggered.");
			return;
		}

		var holdingExceeded = HoldingHours > 0 && _shortEntryTime is DateTimeOffset shortEntry && candle.CloseTime - shortEntry >= TimeSpan.FromHours(HoldingHours);
		var closingHourReached = GmtClosingHour > 0 && IsAfterOrEqualHour(candle.CloseTime, GmtClosingHour + GmtDiff);

		if ((holdingExceeded || closingHourReached) && _shortEntryPrice != 0m)
		{
			if (price < _shortEntryPrice)
			{
				BuyMarket(Math.Abs(Position));
				LogInfo("Short closed with profit due to session or holding limit.");
				return;
			}

			if (!_shortBreakEvenActive)
			{
				_shortBreakEvenActive = true;
				LogInfo("Short switched to break-even mode due to session or holding limit.");
			}
		}

		if (_shortBreakEvenActive && _shortEntryPrice != 0m && price <= _shortEntryPrice)
		{
			BuyMarket(Math.Abs(Position));
			_shortBreakEvenActive = false;
			LogInfo("Short closed at break-even after session limit.");
			return;
		}

		if (EnableReversal && _dailyOpen != 0m)
		{
			var step1 = TrendSteps >= 0 && _dailyHigh - price > riskDistance;
			var step2 = TrendSteps >= 2 && _dailyOpen - _dailyLow >= riskDistance && price - _dailyOpen <= _tenPips;

			if (price > _dailyOpen && (step1 || step2) && rangeTrend == TrendDirections.Up && cciTrend == TrendDirections.Up)
			{
				BuyMarket(Math.Abs(Position));
				LogInfo("Short reversed due to opposite trend confirmation.");
			}
		}
	}

	private void HandleProfitStop()
	{
		if (ProfitStop <= 0m || _tradingSuspended)
		return;

		var realized = PnL - _dayPnLBase;
		var floating = 0m;

		var total = realized + floating;

		if (total >= ProfitStop)
		{
			_tradingSuspended = true;
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();
			LogInfo($"Trading suspended after reaching daily profit stop of {ProfitStop}.");
		}
	}

	private bool CanOpenPositions(ICandleMessage candle)
	{
		if (!EnableAutoTrading || _tradingSuspended)
		return false;

		if (_currentDay is null)
		return false;

		if (!IsWeekday(candle.OpenTime))
		return false;

		if (!IsWithinTradingWindow(candle.OpenTime))
		return false;

		return true;
	}

	private void UpdateDailyLevels(ICandleMessage candle)
	{
		var day = candle.OpenTime.Date;

		if (_currentDay != day)
		{
			_currentDay = day;
			_dailyOpen = candle.OpenPrice;
			_dailyHigh = candle.HighPrice;
			_dailyLow = candle.LowPrice;
			_dayPnLBase = PnL;
			_longBreakEvenActive = false;
			_shortBreakEvenActive = false;
		}
		else
		{
			_dailyHigh = Math.Max(_dailyHigh, candle.HighPrice);
			_dailyLow = Math.Min(_dailyLow, candle.LowPrice);
		}
	}

	private void UpdateCciHistory(decimal cciValue)
	{
		if (_cci.IsFormed)
		{
			_cciHistory.Insert(0, cciValue);
			if (_cciHistory.Count > 3)
			_cciHistory.RemoveAt(_cciHistory.Count - 1);
		}
	}

	private TrendDirections GetCciTrend()
	{
		if (_cciHistory.Count < 3)
		return TrendDirections.Flat;

		var current = _cciHistory[0];
		var previous = _cciHistory[1];
		var older = _cciHistory[2];

		if (current >= previous && previous >= older)
		return TrendDirections.Up;

		if (current <= previous && previous <= older)
		return TrendDirections.Down;

		return TrendDirections.Flat;
	}

	private TrendDirections GetDirectionalTrend(ICandleMessage candle, decimal riskDistance)
	{
		if (_currentDay is null)
		return TrendDirections.Flat;

		var price = candle.ClosePrice;

		if (price > _dailyOpen)
		{
			var step1 = TrendSteps >= 0 && _dailyHigh - price > riskDistance;
			var step2 = TrendSteps >= 2 && _dailyOpen - _dailyLow >= riskDistance && price - _dailyOpen <= _tenPips;
			var step3 = TrendSteps >= 3 && price - _dailyOpen <= _tenPips && candle.ClosePrice > candle.OpenPrice;

			if (step1 || step2 || step3)
			return TrendDirections.Up;
		}
		else if (price < _dailyOpen)
		{
			var step1 = TrendSteps >= 0 && price - _dailyLow > riskDistance;
			var step2 = TrendSteps >= 2 && _dailyHigh - _dailyOpen >= riskDistance && _dailyOpen - price <= _tenPips;
			var step3 = TrendSteps >= 3 && _dailyOpen - price <= _tenPips && candle.ClosePrice < candle.OpenPrice;

			if (step1 || step2 || step3)
			return TrendDirections.Down;
		}

		return TrendDirections.Flat;
	}

	private TrendDirections GetRangeTrend()
	{
		var upDistance = _dailyHigh - _dailyOpen;
		var downDistance = _dailyOpen - _dailyLow;

		if (upDistance > downDistance)
		return TrendDirections.Up;

		if (upDistance < downDistance)
		return TrendDirections.Down;

		return TrendDirections.Flat;
	}

	private bool IsWeekday(DateTimeOffset time)
	{
		var day = time.DayOfWeek;
		return day is not DayOfWeek.Saturday and not DayOfWeek.Sunday;
	}

	private bool IsWithinTradingWindow(DateTimeOffset time)
	{
		var hour = time.Hour;
		var start = NormalizeHour(GmtStartHour + GmtDiff);
		var end = NormalizeHour(GmtEndHour + GmtDiff);

		if (start == end)
		return false;

		return start < end ? hour >= start && hour < end : hour >= start || hour < end;
	}

	private bool IsAfterOrEqualHour(DateTimeOffset time, int targetHour)
	{
		var hour = time.Hour;
		var normalizedTarget = NormalizeHour(targetHour);
		return hour >= normalizedTarget;
	}

	private static int NormalizeHour(int hour)
	{
		var normalized = hour % 24;
		return normalized < 0 ? normalized + 24 : normalized;
	}

	private decimal GetPipSize()
	{
		var step = Security.PriceStep ?? 0.0001m;
		var decimals = Security.Decimals ?? 0;

		if (decimals >= 3)
		return step * 10m;

		return step > 0m ? step : 0.0001m;
	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (Position > 0m)
		{
			_longEntryPrice = _lastClose;
			_longEntryTime = _lastCandleTime;
			_longTakeProfitPrice = TakeProfitPips > 0m ? _longEntryPrice + TakeProfitPips * _pipSize : 0m;
			_longStopPrice = StopLossPips > 0m ? _longEntryPrice - StopLossPips * _pipSize : 0m;
			_longBreakEvenActive = false;
		}
		else
		{
			_longEntryTime = null;
			_longTakeProfitPrice = 0m;
			_longStopPrice = 0m;
			if (Position <= 0m)
			_longEntryPrice = 0m;
		}

		if (Position < 0m)
		{
			_shortEntryPrice = _lastClose;
			_shortEntryTime = _lastCandleTime;
			_shortTakeProfitPrice = TakeProfitPips > 0m ? _shortEntryPrice - TakeProfitPips * _pipSize : 0m;
			_shortStopPrice = StopLossPips > 0m ? _shortEntryPrice + StopLossPips * _pipSize : 0m;
			_shortBreakEvenActive = false;
		}
		else
		{
			_shortEntryTime = null;
			_shortTakeProfitPrice = 0m;
			_shortStopPrice = 0m;
			if (Position >= 0m)
			_shortEntryPrice = 0m;
		}
	}
}