Auf GitHub ansehen

Exp Zeitzonenpivots-Offenem-System Tm Plus Strategie

Diese Strategie ist ein High-Level-StockSharp-Port des Expert Advisors Exp_TimeZonePivotsOpenSystem_Tm_Plus. Er recreiert den proprietären TimeZonePivotsOpenSystem-Indikator, der zwei Ausbruchszonen rund um das tägliche Sessions-Open projiziert und die Pullbacks handelt, die einem Ausbruch folgen. Jede Komponente des ursprünglichen Skripts—Signalverzögerung, Zeitfilter, asymmetrische Ausstiegslogik und die Geldmanagement-Voreinstellungen—wurde auf explizite Parameter abgebildet, damit das Verhalten mit der MQL5-Implementierung konsistent bleibt.

Handelslogik

  1. Zur konfigurierten StartHour zeichnet die Strategie den Session-Eröffnungspreis auf. Zwei dynamische Niveaus werden dann bei OffsetPoints (in Punkten) über und unter diesem Anker gezeichnet.
  2. Immer wenn eine abgeschlossene Kerze über dem oberen Niveau schließt, ist die Strategie:
    • Plant einen Long-Einstieg, der auf der nächsten Kerze ausgeführt wird (unter Beachtung der SignalBar-Verzögerung), nur wenn der aktuelle Balken nicht mehr über der Band liegt.
    • Schließt jede offene Short-Position sofort, wenn SellPosClose aktiviert ist.
  3. Immer wenn eine abgeschlossene Kerze unter dem unteren Niveau schließt, ist die Strategie:
    • Plant einen Short-Einstieg für die nächste Kerze, sofern der aktuelle Balken nicht mehr unter der Band liegt.
    • Schließt jede offene Long-Position sofort, wenn BuyPosClose aktiviert ist.
  4. Einstiege werden beim ersten Update der nächsten Kerze dank TryExecutePendingEntries ausgeführt. Dies entspricht dem ursprünglichen Experten, der die Order bis zum Beginn der neuen Kerze verzögert.

Der Signalverzögerungsparameter SignalBar reproduziert den ursprünglichen CopyBuffer-Shift. Ein Wert von 0 reagiert auf den zuletzt geschlossenen Balken, während 1 einen zusätzlichen Balken wartet, bevor gehandelt wird, was zusätzliche Bestätigung gibt.

Order-Management

  • Stop-Loss / Take-Profit – Die Abstände werden in Punkten (StopLossPoints, TakeProfitPoints) angegeben und unter Verwendung des Instrumentenschritts in Preis umgerechnet. Beide Niveaus werden anhand der Kerzenhochs/-tiefs überwacht, sodass intrabar-Berührungen einen Ausstieg auslösen.
  • Zeitbasierter Ausstieg – Wenn TimeTrade wahr ist, wird die Position nach HoldingMinutes Minuten zwangsliquidiert, was den nTime-Timer aus dem MQL5-Code spiegelt.
  • Manuelle Schließungen – Ausbruchssignale in entgegengesetzter Richtung schließen den laufenden Trade, wenn das entsprechende BuyPosClose- oder SellPosClose-Flag aktiviert ist.

Geldmanagement

Der MoneyMode-Parameter reproduziert die MarginMode-Aufzählung:

  • Lot – festes Volumen gleich MoneyManagement.
  • Balance und FreeMargin – verwenden Kontoeigenkapital- oder freie-Margin-Vielfache (MoneyManagement * Eigenkapital / Preis).
  • LossBalance und LossFreeMargin – risikobasierte Größenbestimmung, die den gewünschten Kapitalanteil durch den Stop-Abstand teilt.

Wenn StopLossPoints auf null gesetzt ist, fallen die Risikomodi auf preisbasierte Größenbestimmung zurück.

Parameter

Parameter Beschreibung Standard
MoneyManagement Basiskoeffizient zur Positionsgrößenbestimmung abhängig von MoneyMode. 0.1
MoneyMode Positionsgrößenmodell (Lot, Balance, FreeMargin, LossBalance, LossFreeMargin). Lot
StopLossPoints Stop-Loss-Abstand in Punkten vom Ausführungspreis. 1000
TakeProfitPoints Take-Profit-Abstand in Punkten vom Ausführungspreis. 2000
DeviationPoints Informativer Parameter aus dem Experten (Slippage-Einstellung in Punkten). 10
BuyPosOpen / SellPosOpen Long- und Short-Einstiege aktivieren oder deaktivieren. true
BuyPosClose / SellPosClose Dem entgegengesetzten Ausbruch erlauben, Positionen zwangsweise zu schließen. true
TimeTrade Den maximalen Haltezeit-Filter aktivieren. true
HoldingMinutes Maximale Positionslebensdauer in Minuten. 720
OffsetPoints Abstand der Pivot-Bänder vom Session-Open in Punkten. 200
SignalBar Anzahl der Balken zur Verzögerung der Signalauswertung (0 = letzter geschlossener Balken). 1
CandleType Haupt-Zeitrahmen zur Berechnung des Indikators. TimeSpan.FromHours(1).TimeFrame()
StartHour Stunde des Tages (0-23), die den Session-Eröffnungspreis definiert. 0

Verwendungshinweise

  • Die Strategie setzt voraus, dass das Wertpapier einen gültigen PriceStep bereitstellt. Wenn dem Instrument diese Metadaten fehlen, wird ein Fallback von 0.0001 verwendet.
  • Da Einstiege beim ersten Update einer neuen Kerze ausgelöst werden, folgt der tatsächliche Ausführungspreis dem Markt zu diesem Zeitpunkt, genau wie beim Experten, was in schnellen Märkten vom theoretischen Eröffnungspreis abweichen kann.
  • Um die ursprüngliche Indikator-Überlagerung zu replizieren, halten Sie den Backtest-Zeitrahmen bei oder unter H1, da das MQL5-Skript nur auf stündlichen oder niedrigeren Perioden arbeitet.
  • Setzen Sie SignalBar auf 0 für reaktiveres Verhalten oder auf 1 (Standard), um nach einem Ausbruch eine extra Kerze zu warten.
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>
/// Strategy that replicates the Time Zone Pivots Open System expert from MetaTrader.
/// It follows the session open price and reacts when candles close above or below the
/// upper and lower offset bands while respecting the original money management rules.
/// </summary>
public class ExpTimeZonePivotsOpenSystemTmPlusStrategy : Strategy
{
	// Parameters from the original expert controlling size, stops and permissions.
	private readonly StrategyParam<decimal> _moneyManagement;
	private readonly StrategyParam<MoneyManagementModes> _moneyMode;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _deviationPoints;
	private readonly StrategyParam<bool> _allowBuyOpen;
	private readonly StrategyParam<bool> _allowSellOpen;
	private readonly StrategyParam<bool> _allowBuyClose;
	private readonly StrategyParam<bool> _allowSellClose;
	private readonly StrategyParam<bool> _useTimeExit;
	private readonly StrategyParam<int> _holdingMinutes;
	private readonly StrategyParam<decimal> _offsetPoints;
	private readonly StrategyParam<int> _signalBar;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _startHour;

	// Rolling buffer that stores recent candle states for the indicator recreation.
	private readonly List<ZoneSnapshot> _zoneHistory = new();

	// Session level tracking.
	private DateTime? _lastSessionDate;
	private decimal? _sessionOpenPrice;
	private decimal? _upperBand;
	private decimal? _lowerBand;
	private bool _sessionTradeTaken;
	private DateTimeOffset? _lastEntryDate;

	// Pending entry scheduling.
	private bool _pendingLongEntry;
	private bool _pendingShortEntry;
	private DateTimeOffset? _longSignalTime;
	private DateTimeOffset? _shortSignalTime;
	private DateTimeOffset? _lastLongSignalOrigin;
	private DateTimeOffset? _lastShortSignalOrigin;
	private DateTimeOffset? _currentCandleOpen;

	// Position bookkeeping for exit controls.
	private DateTimeOffset? _longEntryTime;
	private DateTimeOffset? _shortEntryTime;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortStopPrice;
	private decimal? _shortTakePrice;

	// Cached timeframe of the selected candle series.
	private TimeSpan? _timeFrame;

	public decimal MoneyManagement
	{
		get => _moneyManagement.Value;
		set => _moneyManagement.Value = value;
	}

	public MoneyManagementModes MoneyMode
	{
		get => _moneyMode.Value;
		set => _moneyMode.Value = value;
	}

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public decimal DeviationPoints
	{
		get => _deviationPoints.Value;
		set => _deviationPoints.Value = value;
	}

	public bool BuyPosOpen
	{
		get => _allowBuyOpen.Value;
		set => _allowBuyOpen.Value = value;
	}

	public bool SellPosOpen
	{
		get => _allowSellOpen.Value;
		set => _allowSellOpen.Value = value;
	}

	public bool BuyPosClose
	{
		get => _allowBuyClose.Value;
		set => _allowBuyClose.Value = value;
	}

	public bool SellPosClose
	{
		get => _allowSellClose.Value;
		set => _allowSellClose.Value = value;
	}

	public bool TimeTrade
	{
		get => _useTimeExit.Value;
		set => _useTimeExit.Value = value;
	}

	public int HoldingMinutes
	{
		get => _holdingMinutes.Value;
		set => _holdingMinutes.Value = value;
	}

	public decimal OffsetPoints
	{
		get => _offsetPoints.Value;
		set => _offsetPoints.Value = value;
	}

	public int SignalBar
	{
		get => _signalBar.Value;
		set => _signalBar.Value = value;
	}

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

	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	public ExpTimeZonePivotsOpenSystemTmPlusStrategy()
	{
		_moneyManagement = Param(nameof(MoneyManagement), 0.1m)
			.SetDisplay("Money Management", "Base value used for position sizing", "Trading")
			.SetGreaterThanZero();

		_moneyMode = Param(nameof(MoneyMode), MoneyManagementModes.Lot)
			.SetDisplay("Money Mode", "Position sizing model", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000m)
			.SetDisplay("Stop Loss (points)", "Distance from entry to stop loss expressed in points", "Risk")
			.SetNotNegative();

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000m)
			.SetDisplay("Take Profit (points)", "Distance from entry to take profit expressed in points", "Risk")
			.SetNotNegative();

		_deviationPoints = Param(nameof(DeviationPoints), 10m)
			.SetDisplay("Allowed Deviation", "Maximum acceptable price deviation for entries", "Risk")
			.SetNotNegative();

		_allowBuyOpen = Param(nameof(BuyPosOpen), true)
			.SetDisplay("Enable Long Entries", "Allow opening long positions", "Trading");

		_allowSellOpen = Param(nameof(SellPosOpen), true)
			.SetDisplay("Enable Short Entries", "Allow opening short positions", "Trading");

		_allowBuyClose = Param(nameof(BuyPosClose), true)
			.SetDisplay("Enable Long Exits", "Allow closing long positions on opposite signals", "Trading");

		_allowSellClose = Param(nameof(SellPosClose), true)
			.SetDisplay("Enable Short Exits", "Allow closing short positions on opposite signals", "Trading");

		_useTimeExit = Param(nameof(TimeTrade), true)
			.SetDisplay("Use Time Exit", "Close positions after a fixed holding time", "Risk");

		_holdingMinutes = Param(nameof(HoldingMinutes), 720)
			.SetDisplay("Holding Minutes", "Maximum position lifetime in minutes", "Risk")
			.SetNotNegative();

		_offsetPoints = Param(nameof(OffsetPoints), 200m)
			.SetDisplay("Offset (points)", "Distance from session open that defines the pivot zones", "Indicator")
			.SetNotNegative();

		_signalBar = Param(nameof(SignalBar), 1)
			.SetDisplay("Signal Bar", "Number of bars to delay the signal evaluation", "Indicator")
			.SetNotNegative();

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe for calculations", "Indicator");

		_startHour = Param(nameof(StartHour), 0)
			.SetDisplay("Session Start Hour", "Hour of day used to anchor the session open price", "Indicator")
			.SetNotNegative()
			;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	protected override void OnReseted()
	{
		base.OnReseted();

		_zoneHistory.Clear();
		_lastSessionDate = null;
		_sessionOpenPrice = null;
		_upperBand = null;
		_lowerBand = null;
		_pendingLongEntry = false;
		_pendingShortEntry = false;
		_longSignalTime = null;
		_shortSignalTime = null;
		_lastLongSignalOrigin = null;
		_lastShortSignalOrigin = null;
		_currentCandleOpen = null;
		_longEntryTime = null;
		_shortEntryTime = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;
		_longStopPrice = null;
		_longTakePrice = null;
		_shortStopPrice = null;
		_shortTakePrice = null;
		_timeFrame = CandleType.Arg as TimeSpan?;
		_sessionTradeTaken = false;
		_lastEntryDate = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_timeFrame = CandleType.Arg as TimeSpan?;

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

		// Enable loss protection guard as required by the framework.
		StartProtection(null, null);
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Detect new candle openings to trigger delayed entries on the first update.
		if (_currentCandleOpen != candle.OpenTime)
		{
			_currentCandleOpen = candle.OpenTime;
			TryExecutePendingEntries(candle);
		}

		// Only finished candles contribute to the indicator logic and trade decisions.
		if (candle.State != CandleStates.Finished)
			return;

		// Refresh the session reference and pre-compute band levels.
		UpdateSessionReference(candle);

		// Memorise the current candle classification for delayed evaluations.
		var snapshot = new ZoneSnapshot
		{
			State = DetermineState(candle),
			OpenTime = candle.OpenTime,
			CloseTime = candle.CloseTime
		};

		_zoneHistory.Insert(0, snapshot);

		var maxHistory = Math.Max(5, SignalBar + 3);
		while (_zoneHistory.Count > maxHistory)
			_zoneHistory.RemoveAt(_zoneHistory.Count - 1);

		// Ensure we have enough candles to evaluate the signal and confirmation offsets.
		if (_zoneHistory.Count <= SignalBar + 1)
		{
			ManageStops(candle);
			HandleTimeExit(candle.CloseTime);
			return;
		}

		var signalSnapshot = _zoneHistory[SignalBar];
		var confirmSnapshot = _zoneHistory[SignalBar + 1];

		if (signalSnapshot == null || confirmSnapshot == null)
		{
			ManageStops(candle);
			HandleTimeExit(candle.CloseTime);
			return;
		}

		var closeLong = false;
		var closeShort = false;

		// Previous candle closed above the upper band – schedule long entry and close shorts.
		if (confirmSnapshot.State == ZoneSignals.Above)
		{
			if (SellPosClose)
				closeShort = true;

			if (BuyPosOpen && signalSnapshot.State != ZoneSignals.Above && (_lastLongSignalOrigin != confirmSnapshot.CloseTime))
			{
				_pendingLongEntry = true;
				_longSignalTime = confirmSnapshot.CloseTime + (_timeFrame ?? TimeSpan.Zero);
				_lastLongSignalOrigin = confirmSnapshot.CloseTime;
			}
		}
		// Previous candle closed below the lower band – schedule short entry and close longs.
		else if (confirmSnapshot.State == ZoneSignals.Below)
		{
			if (BuyPosClose)
				closeLong = true;

			if (SellPosOpen && signalSnapshot.State != ZoneSignals.Below && (_lastShortSignalOrigin != confirmSnapshot.CloseTime))
			{
				_pendingShortEntry = true;
				_shortSignalTime = confirmSnapshot.CloseTime + (_timeFrame ?? TimeSpan.Zero);
				_lastShortSignalOrigin = confirmSnapshot.CloseTime;
			}
		}

		if (closeLong && Position > 0m)
		{
			SellMarket(Position);
			_longEntryTime = null;
			_longEntryPrice = null;
			_longStopPrice = null;
			_longTakePrice = null;
		}

		if (closeShort && Position < 0m)
		{
			BuyMarket(Math.Abs(Position));
			_shortEntryTime = null;
			_shortEntryPrice = null;
			_shortStopPrice = null;
			_shortTakePrice = null;
		}

		ManageStops(candle);
		HandleTimeExit(candle.CloseTime);
	}

	// Execute pending entries once the new candle that should host the trade begins.
	private void TryExecutePendingEntries(ICandleMessage candle)
	{
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (Position != 0m)
			return;

		if (_sessionTradeTaken)
			return;

		if (_lastEntryDate.HasValue && candle.OpenTime.Date <= _lastEntryDate.Value.Date.AddDays(4))
			return;

		var opened = false;

		if (_pendingLongEntry && BuyPosOpen)
		{
			if (!_longSignalTime.HasValue || candle.OpenTime >= _longSignalTime.Value)
			{
				var entryPrice = candle.OpenPrice;
				var volume = GetEntryVolume(true, entryPrice);

				if (volume > 0m)
				{
					_longEntryPrice = entryPrice;
					BuyMarket(volume);
					_pendingLongEntry = false;
					_longSignalTime = null;
					_sessionTradeTaken = true;
					_lastEntryDate = candle.OpenTime;
					opened = true;
				}
			}
		}

		if (!opened && _pendingShortEntry && SellPosOpen)
		{
			if (!_shortSignalTime.HasValue || candle.OpenTime >= _shortSignalTime.Value)
			{
				var entryPrice = candle.OpenPrice;
				var volume = GetEntryVolume(false, entryPrice);

				if (volume > 0m)
				{
					_shortEntryPrice = entryPrice;
					SellMarket(volume);
					_pendingShortEntry = false;
					_shortSignalTime = null;
					_sessionTradeTaken = true;
					_lastEntryDate = candle.OpenTime;
				}
			}
		}
	}

	// Monitor stop-loss and take-profit levels intrabar using candle extremes.
	private void ManageStops(ICandleMessage candle)
	{
		var volume = Math.Abs(Position);

		if (volume <= 0m)
			return;

		if (Position > 0m)
		{
			if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
			{
				SellMarket(volume);
				_longEntryTime = null;
				_longEntryPrice = null;
				_longStopPrice = null;
				_longTakePrice = null;
			}
			else if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
			{
				SellMarket(volume);
				_longEntryTime = null;
				_longEntryPrice = null;
				_longStopPrice = null;
				_longTakePrice = null;
			}
		}
		else if (Position < 0m)
		{
			if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
			{
				BuyMarket(volume);
				_shortEntryTime = null;
				_shortEntryPrice = null;
				_shortStopPrice = null;
				_shortTakePrice = null;
			}
			else if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
			{
				BuyMarket(volume);
				_shortEntryTime = null;
				_shortEntryPrice = null;
				_shortStopPrice = null;
				_shortTakePrice = null;
			}
		}
	}

	// Implement the time based exit from the MQL5 code.
	private void HandleTimeExit(DateTimeOffset time)
	{
		if (!TimeTrade)
			return;

		var holdMinutes = HoldingMinutes;
		if (holdMinutes <= 0)
			return;

		var threshold = TimeSpan.FromMinutes(holdMinutes);
		var volume = Math.Abs(Position);

		if (volume <= 0m)
			return;

		if (Position > 0m && _longEntryTime.HasValue && time - _longEntryTime.Value >= threshold)
		{
			SellMarket(volume);
			_longEntryTime = null;
			_longEntryPrice = null;
			_longStopPrice = null;
			_longTakePrice = null;
		}
		else if (Position < 0m && _shortEntryTime.HasValue && time - _shortEntryTime.Value >= threshold)
		{
			BuyMarket(volume);
			_shortEntryTime = null;
			_shortEntryPrice = null;
			_shortStopPrice = null;
			_shortTakePrice = null;
		}
	}

	// Update the session open reference when the configured hour is reached.
	private void UpdateSessionReference(ICandleMessage candle)
	{
		var openTime = candle.OpenTime;
		var currentDate = openTime.Date;

		if ((!_lastSessionDate.HasValue || _lastSessionDate.Value != currentDate) && openTime.Hour == StartHour)
		{
			_sessionOpenPrice = candle.OpenPrice;
			_lastSessionDate = currentDate;
			_zoneHistory.Clear();
			_pendingLongEntry = false;
			_pendingShortEntry = false;
			_longSignalTime = null;
			_shortSignalTime = null;
			_lastLongSignalOrigin = null;
			_lastShortSignalOrigin = null;
			_sessionTradeTaken = false;
		}

		if (_sessionOpenPrice.HasValue)
		{
			var step = GetPriceStep();
			var offset = OffsetPoints * step;

			_upperBand = _sessionOpenPrice + offset;
			_lowerBand = _sessionOpenPrice - offset;
		}
		else
		{
			_upperBand = null;
			_lowerBand = null;
		}
	}

	// Classify the candle relative to the offset bands.
	private ZoneSignals DetermineState(ICandleMessage candle)
	{
		if (!_sessionOpenPrice.HasValue || !_upperBand.HasValue || !_lowerBand.HasValue)
			return ZoneSignals.Inside;

		if (candle.ClosePrice > _upperBand.Value)
			return ZoneSignals.Above;

		if (candle.ClosePrice < _lowerBand.Value)
			return ZoneSignals.Below;

		return ZoneSignals.Inside;
	}

	// Translate the money management mode into an executable volume.
	private decimal GetEntryVolume(bool isLong, decimal price)
	{
		if (price <= 0m)
			return 0m;

		var step = GetPriceStep();
		var stopDistance = StopLossPoints > 0m ? StopLossPoints * step : 0m;
		var capital = Portfolio?.CurrentValue ?? 0m;
		var mmValue = MoneyManagement;

		switch (MoneyMode)
		{
			case MoneyManagementModes.Lot:
				return mmValue;
			case MoneyManagementModes.Balance:
			case MoneyManagementModes.FreeMargin:
				return capital > 0m ? capital * mmValue / price : 0m;
			case MoneyManagementModes.LossBalance:
			case MoneyManagementModes.LossFreeMargin:
				if (stopDistance > 0m)
					return capital > 0m ? capital * mmValue / stopDistance : 0m;

				return capital > 0m ? capital * mmValue / price : 0m;
			default:
				return mmValue;
		}
	}

	// Retrieve the minimum price step for the configured security.
	private decimal GetPriceStep()
	{
		var security = Security;
		if (security == null)
			return 0.0001m;

		if (security.PriceStep > 0m)
			return security.PriceStep.Value;

		return 0.0001m;
	}

	// Helper to compute stop-loss and take-profit levels around the fill price.
	private decimal? CalculateStopPrice(bool isLong, decimal? entryPrice)
	{
		if (!entryPrice.HasValue || StopLossPoints <= 0m)
			return null;

		var distance = StopLossPoints * GetPriceStep();
		return isLong ? entryPrice - distance : entryPrice + distance;
	}

	private decimal? CalculateTakePrice(bool isLong, decimal? entryPrice)
	{
		if (!entryPrice.HasValue || TakeProfitPoints <= 0m)
			return null;

		var distance = TakeProfitPoints * GetPriceStep();
		return isLong ? entryPrice + distance : entryPrice - distance;
	}

	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		if (Position > 0m && trade.Order.Side == Sides.Buy)
		{
			_longEntryTime = trade.Trade.ServerTime;
			_longEntryPrice = trade.Trade.Price;
			_longStopPrice = CalculateStopPrice(true, _longEntryPrice);
			_longTakePrice = CalculateTakePrice(true, _longEntryPrice);
		}
		else if (Position < 0m && trade.Order.Side == Sides.Sell)
		{
			_shortEntryTime = trade.Trade.ServerTime;
			_shortEntryPrice = trade.Trade.Price;
			_shortStopPrice = CalculateStopPrice(false, _shortEntryPrice);
			_shortTakePrice = CalculateTakePrice(false, _shortEntryPrice);
		}

		if (Position == 0m)
		{
			_longEntryTime = null;
			_shortEntryTime = null;
			_longEntryPrice = null;
			_shortEntryPrice = null;
			_longStopPrice = null;
			_shortStopPrice = null;
			_longTakePrice = null;
			_shortTakePrice = null;
		}
	}

	public enum MoneyManagementModes
	{
		FreeMargin,
		Balance,
		LossFreeMargin,
		LossBalance,
		Lot
	}

	private enum ZoneSignals
	{
		Inside,
		Above,
		Below
	}

	private sealed class ZoneSnapshot
	{
		public ZoneSignals State { get; init; }
		public DateTimeOffset OpenTime { get; init; }
		public DateTimeOffset CloseTime { get; init; }
	}
}