Auf GitHub ansehen

True Scalper Gewinnabsicherungs-Strategie

Überblick

Die True Scalper Gewinnabsicherungs-Strategie ist ein StockSharp-Port des MetaTrader 5 Expert Advisors "True Scalper Profit Lock". Die Strategie konzentriert sich auf ultrakunfristigen Handel mit schnellen exponentiellen gleitenden Durchschnitten, einem Zwei-Perioden-RSI-Filter und einer Gewinnschutzroutine, die Stops auf Break-Even verschiebt. Zusätzliche "Abandon"-Logik zwingt die Strategie, Trades zu schließen, die das Ziel innerhalb einer vordefinierten Anzahl von Kerzen nicht erreichen.

Die Implementierung abonniert einen einzelnen Kerzenstrom und bewertet nur abgeschlossene Kerzen. Sie ist für Intraday-Scalping konzipiert, aber alle Parameter sind vollständig anpassbar, sodass sie auf andere Zeitrahmen oder Instrumente angepasst werden kann.

Indikatoren und Daten

  • EMA (schnell) – Standardlänge 3, dient als bullischer Trigger beim Kreuzen über die langsame EMA.
  • EMA (langsam) – Standardlänge 7, definiert die kurzfristige Trendrichtung.
  • RSI – Standardlänge 2 mit auswählbarem Entscheidungsmodus:
    • Methode A (standardmäßig deaktiviert) reagiert auf den RSI, der den Schwellenwert von der vorherigen Kerze kreuzt.
    • Methode B (standardmäßig aktiviert) verfolgt die RSI-Polarität relativ zum Schwellenwert.
  • Kerzen – Standard-Zeitrahmen ist 1 Minute, konfigurierbar über den CandleType-Parameter.

Einstiegslogik

  1. Berechnen Sie die schnelle EMA, langsame EMA und RSI auf der neuesten abgeschlossenen Kerze.
  2. Bewerten Sie den RSI-Zustand:
    • Methode A: RSI-Polarität nur setzen, wenn der Schwellenwert zwischen zwei aufeinanderfolgenden Kerzen gekreuzt wird.
    • Methode B: RSI-Polarität setzen, je nachdem ob der Wert über oder unter dem Schwellenwert liegt.
  3. Kauf-Setup – ausgelöst, wenn die schnelle EMA mindestens einen Preisschritt über der langsamen EMA liegt und der RSI negative Polarität anzeigt. Wenn die Abandon-Logik eine Umkehr zu Long erzwungen hat, wird der Trade auch unabhängig von den aktuellen Signalen eröffnet.
  4. Verkauf-Setup – ausgelöst, wenn die schnelle EMA mindestens einen Preisschritt unter der langsamen EMA liegt und der RSI positive Polarität anzeigt, oder wenn eine Abandon-Umkehr einen Short-Einstieg erzwingt.
  5. Positionsumkehrungen werden durch Senden der Differenz, die benötigt wird, um die Nettoposition in einer einzelnen Marktorder zu wenden, behandelt.

Ausstiegslogik

  • Stop Loss / Take Profit – in Preisschritten konfiguriert (StopLossPoints, TakeProfitPoints) und sofort nach dem Einstieg angewendet.
  • Gewinnabsicherung – wenn aktiviert, wird der Stop auf Break-Even plus einem Offset (BreakEvenPoints) verschoben, sobald der offene Trade den angegebenen Gewinn (BreakEvenTriggerPoints) angesammelt hat. Die Routine funktioniert sowohl für Long- als auch Short-Positionen und läuft nur einmal pro Trade.
  • Abandon-Logik – verfolgt die Anzahl der abgeschlossenen Kerzen seit dem Einstieg:
    • Methode A: schließt den Trade nach AbandonBars Kerzen und setzt ein Flag, um bei der nächsten Gelegenheit eine Position in entgegengesetzter Richtung zu eröffnen.
    • Methode B: schließt die Position nach dem Timeout, lässt aber signalbasierte Richtungsauswahl unberührt.
    • Methode A hat Priorität, wenn beide Methoden aktiviert sind.
  • Manuelle Ausstiege werden mit Marktorders (via ClosePosition) erteilt und setzen den Trade-Zustand automatisch zurück.

Geldverwaltung

  • Wenn UseMoneyManagement aktiviert ist, wird die Positionsgröße aus dem Portfolio-Saldo abgeleitet: Ceiling(Balance * RiskPercent / 10000) / 10.
  • Das verwaltete Volumen ist auf die ursprünglichen MT5-Regeln begrenzt: Mindest-Fallback auf InitialVolume, Werte über 1 Lot aufgerundet, optionaler Mini-Konto-Multiplikator, Hartkappung bei 100 Lots.
  • Wenn deaktiviert, verwendet die Strategie das feste InitialVolume für jede Order.

Parameter

  • InitialVolume – Basis-Lot-Größe, wenn Geldverwaltung deaktiviert ist.
  • TakeProfitPoints / StopLossPoints – Distanz in Security.PriceStep-Einheiten.
  • FastPeriod, SlowPeriod, RsiLength, RsiThreshold – Indikatorkonfiguration.
  • UseRsiMethodA, UseRsiMethodB – RSI-Entscheidungslogik umschalten.
  • UseAbandonMethodA, UseAbandonMethodB, AbandonBars – Timeout-Management konfigurieren.
  • UseMoneyManagement, RiskPercent, LiveTrading, IsMiniAccount – Risikogrößenoptionen, die mit dem MT5-Expert-Advisor abgestimmt sind.
  • UseProfitLock, BreakEvenTriggerPoints, BreakEvenPoints – Break-Even-Parameter.
  • MaxPositions – für Kompatibilität mit der MQL-Version beibehalten (der StockSharp-Port verwaltet eine einzelne Nettoposition pro Instrument).
  • CandleType – Zeitrahmen oder benutzerdefinierter Kerzentyp für die Signalerzeugung.

Verwendungshinweise

  • Hängen Sie die Strategie an ein einzelnes Instrument; der GetWorkingSecurities-Override abonniert automatisch den ausgewählten Kerzentyp.
  • Gewinnabsicherungs- und Abandon-Funktionen hängen von abgeschlossenen Kerzen ab; intrabar Preisausschläge, die innerhalb derselben Kerze zurückkehren, werden ignoriert.
  • Der ursprüngliche MT5-Parameter Slippage wurde im Quellcode nicht verwendet und ist daher nicht vorhanden.
  • Passen Sie Security.PriceStep oder die schrittbasierten Parameter entsprechend dem gehandelten Instrument an, um die beabsichtigten Pip-Distanzen zu erhalten.

Konvertierungsunterschiede

  • StockSharp operiert auf Nettopositionen, sodass keine gleichzeitigen mehrfachen Positionen eröffnet werden, auch wenn MaxPositions größer als eins ist. Dies spiegelt das typische Netting-Verhalten des ursprünglichen Experts wider, wenn maxTradesPerPair gleich 1 ist.
  • Das Ordermanagement verwendet BuyMarket-, SellMarket- und ClosePosition-Helper anstelle direkter Ticket-Manipulation.
  • Indikatordaten werden über Bind-Callbacks geliefert, um manuellen Pufferzugriff zu vermeiden.

Testempfehlungen

  • Validieren Sie das Verhalten auf historischen Daten mit demselben Zeitrahmen, der im ursprünglichen EA verwendet wurde (1-Minuten-Kerzen).
  • Optimieren Sie TakeProfitPoints, StopLossPoints und BreakEvenTriggerPoints für das Zielinstrument, da diese für Forex-Kurse abgestimmt wurden.
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>
/// True Scalper Profit Lock strategy converted from MetaTrader 5.
/// Combines short-term exponential moving averages with RSI filters, profit locking and abandon logic.
/// </summary>
public class TrueScalperProfitLockStrategy : Strategy
{
	private readonly StrategyParam<decimal> _initialVolume;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<decimal> _rsiThreshold;
	private readonly StrategyParam<bool> _useRsiMethodA;
	private readonly StrategyParam<bool> _useRsiMethodB;
	private readonly StrategyParam<bool> _useAbandonMethodA;
	private readonly StrategyParam<bool> _useAbandonMethodB;
	private readonly StrategyParam<int> _abandonBars;
	private readonly StrategyParam<bool> _useMoneyManagement;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<bool> _useProfitLock;
	private readonly StrategyParam<decimal> _breakEvenTriggerPoints;
	private readonly StrategyParam<decimal> _breakEvenPoints;
	private readonly StrategyParam<bool> _liveTrading;
	private readonly StrategyParam<bool> _isMiniAccount;
	private readonly StrategyParam<int> _maxPositions;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _entryPrice;
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private decimal? _previousRsi;
	private decimal _currentVolume;
	private bool _isLongPosition;
	private bool _pendingReverseToBuy;
	private bool _pendingReverseToSell;
	private int _barsSinceEntry;
	private DateTimeOffset? _lastCandleTime;
	private bool _breakEvenApplied;

	/// <summary>
	/// Base order size expressed in lots.
	/// </summary>
	public decimal InitialVolume
	{
		get => _initialVolume.Value;
		set => _initialVolume.Value = value;
	}

	/// <summary>
	/// Take profit distance in price steps.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Stop loss distance in price steps.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// RSI calculation length.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// RSI decision threshold.
	/// </summary>
	public decimal RsiThreshold
	{
		get => _rsiThreshold.Value;
		set => _rsiThreshold.Value = value;
	}

	/// <summary>
	/// Enable RSI crossing logic.
	/// </summary>
	public bool UseRsiMethodA
	{
		get => _useRsiMethodA.Value;
		set => _useRsiMethodA.Value = value;
	}

	/// <summary>
	/// Enable RSI polarity logic.
	/// </summary>
	public bool UseRsiMethodB
	{
		get => _useRsiMethodB.Value;
		set => _useRsiMethodB.Value = value;
	}

	/// <summary>
	/// Force reverse direction after abandon timeout.
	/// </summary>
	public bool UseAbandonMethodA
	{
		get => _useAbandonMethodA.Value;
		set => _useAbandonMethodA.Value = value;
	}

	/// <summary>
	/// Close the trade after abandon timeout without forcing direction.
	/// </summary>
	public bool UseAbandonMethodB
	{
		get => _useAbandonMethodB.Value;
		set => _useAbandonMethodB.Value = value;
	}

	/// <summary>
	/// Number of finished candles before abandon logic triggers.
	/// </summary>
	public int AbandonBars
	{
		get => _abandonBars.Value;
		set => _abandonBars.Value = value;
	}

	/// <summary>
	/// Enable balance based position sizing.
	/// </summary>
	public bool UseMoneyManagement
	{
		get => _useMoneyManagement.Value;
		set => _useMoneyManagement.Value = value;
	}

	/// <summary>
	/// Risk percentage used in money management.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Enable break even stop adjustment.
	/// </summary>
	public bool UseProfitLock
	{
		get => _useProfitLock.Value;
		set => _useProfitLock.Value = value;
	}

	/// <summary>
	/// Profit distance that triggers break even move.
	/// </summary>
	public decimal BreakEvenTriggerPoints
	{
		get => _breakEvenTriggerPoints.Value;
		set => _breakEvenTriggerPoints.Value = value;
	}

	/// <summary>
	/// Stop offset applied once break even activates.
	/// </summary>
	public decimal BreakEvenPoints
	{
		get => _breakEvenPoints.Value;
		set => _breakEvenPoints.Value = value;
	}

	/// <summary>
	/// Use live trading sizing adjustments.
	/// </summary>
	public bool LiveTrading
	{
		get => _liveTrading.Value;
		set => _liveTrading.Value = value;
	}

	/// <summary>
	/// Treat account as mini when applying live adjustments.
	/// </summary>
	public bool IsMiniAccount
	{
		get => _isMiniAccount.Value;
		set => _isMiniAccount.Value = value;
	}

	/// <summary>
	/// Maximum simultaneous trades allowed by the original logic.
	/// </summary>
	public int MaxPositions
	{
		get => _maxPositions.Value;
		set => _maxPositions.Value = value;
	}

	/// <summary>
	/// Candle type used for signal generation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="TrueScalperProfitLockStrategy"/> class.
	/// </summary>
	public TrueScalperProfitLockStrategy()
	{
		_initialVolume = Param(nameof(InitialVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Lots", "Base trade volume", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 44m)
		.SetGreaterThanZero()
		.SetDisplay("Take Profit", "Take profit distance in steps", "Risk")
		
		.SetOptimize(20m, 80m, 5m);

		_stopLossPoints = Param(nameof(StopLossPoints), 90m)
		.SetGreaterThanZero()
		.SetDisplay("Stop Loss", "Stop loss distance in steps", "Risk")
		
		.SetOptimize(50m, 150m, 10m);

		_fastPeriod = Param(nameof(FastPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Fast EMA length", "Signals");

		_slowPeriod = Param(nameof(SlowPeriod), 7)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Slow EMA length", "Signals");

		_rsiLength = Param(nameof(RsiLength), 2)
		.SetGreaterThanZero()
		.SetDisplay("RSI Length", "RSI calculation length", "Signals");

		_rsiThreshold = Param(nameof(RsiThreshold), 50m)
		.SetDisplay("RSI Threshold", "RSI boundary for polarity", "Signals")
		
		.SetOptimize(40m, 60m, 5m);

		_useRsiMethodA = Param(nameof(UseRsiMethodA), true)
		.SetDisplay("RSI Method A", "Use RSI crossing logic", "Signals");

		_useRsiMethodB = Param(nameof(UseRsiMethodB), false)
		.SetDisplay("RSI Method B", "Use RSI polarity logic", "Signals");

		_useAbandonMethodA = Param(nameof(UseAbandonMethodA), true)
		.SetDisplay("Abandon Method A", "Force reverse after timeout", "Management");

		_useAbandonMethodB = Param(nameof(UseAbandonMethodB), false)
		.SetDisplay("Abandon Method B", "Only close after timeout", "Management");

		_abandonBars = Param(nameof(AbandonBars), 101)
		.SetGreaterThanZero()
		.SetDisplay("Abandon Bars", "Bars before abandon logic", "Management");

		_useMoneyManagement = Param(nameof(UseMoneyManagement), true)
		.SetDisplay("Money Management", "Enable balance based sizing", "Risk");

		_riskPercent = Param(nameof(RiskPercent), 2m)
		.SetGreaterThanZero()
		.SetDisplay("Risk %", "Risk percentage per trade", "Risk");

		_useProfitLock = Param(nameof(UseProfitLock), true)
		.SetDisplay("Use Profit Lock", "Move stop to break even", "Risk");

		_breakEvenTriggerPoints = Param(nameof(BreakEvenTriggerPoints), 25m)
		.SetGreaterThanZero()
		.SetDisplay("BreakEven Trigger", "Profit distance before break even", "Risk");

		_breakEvenPoints = Param(nameof(BreakEvenPoints), 3m)
		.SetGreaterThanZero()
		.SetDisplay("BreakEven Offset", "Offset applied at break even", "Risk");

		_liveTrading = Param(nameof(LiveTrading), false)
		.SetDisplay("Live Trading", "Apply live sizing adjustments", "Risk");

		_isMiniAccount = Param(nameof(IsMiniAccount), false)
		.SetDisplay("Mini Account", "Treat account as mini", "Risk");

		_maxPositions = Param(nameof(MaxPositions), 1)
		.SetGreaterThanZero()
		.SetDisplay("Max Positions", "Maximum simultaneous trades", "Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
		.SetDisplay("Candle Type", "Candle type for processing", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_previousRsi = null;
		_currentVolume = 0m;
		_isLongPosition = false;
		_pendingReverseToBuy = false;
		_pendingReverseToSell = false;
		_barsSinceEntry = 0;
		_lastCandleTime = null;
		_breakEvenApplied = false;
	}

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

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

		var fastEma = new EMA { Length = FastPeriod };
		var slowEma = new EMA { Length = SlowPeriod };
		var rsi = new RSI { Length = RsiLength };

		SubscribeCandles(CandleType)
		.Bind(fastEma, slowEma, rsi, ProcessCandle)
		.Start();
	}

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

		UpdateBarCounter(candle);

		var step = GetPriceStep();

		ApplyAbandonLogic();

		if (Position != 0)
		{
			ApplyProfitLock(step, candle);

			if (TryExitByTargets(candle))
			{
				_pendingReverseToBuy = false;
				_pendingReverseToSell = false;
			}
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousRsi = rsi;
			return;
		}

		var (rsiPositive, rsiNegative) = EvaluateRsiSignals(rsi);

		var buySignal = fastEma > slowEma + step && rsiNegative;
		var sellSignal = fastEma < slowEma - step && rsiPositive;

		TryEnterPosition(candle, step, buySignal, sellSignal);

		_previousRsi = rsi;
	}

	private void UpdateBarCounter(ICandleMessage candle)
	{
		if (_lastCandleTime == candle.OpenTime)
		return;

		if (Position != 0 && _lastCandleTime != null)
		_barsSinceEntry++;
		else if (Position == 0)
		_barsSinceEntry = 0;

		_lastCandleTime = candle.OpenTime;
	}

	private decimal GetPriceStep()
	{
		var step = Security?.PriceStep ?? 0m;

		if (step <= 0)
		step = 0.0001m;

		return step;
	}

	private void ApplyAbandonLogic()
	{
		if (Position == 0 || AbandonBars <= 0)
		return;

		if (_barsSinceEntry < AbandonBars)
		return;

		if (UseAbandonMethodA)
		{
			if (_isLongPosition && Position > 0)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				ResetTradeState();
				_pendingReverseToSell = true;
				_pendingReverseToBuy = false;
			}
			else if (!_isLongPosition && Position < 0)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				ResetTradeState();
				_pendingReverseToBuy = true;
				_pendingReverseToSell = false;
			}
		}
		else if (UseAbandonMethodB)
		{
			if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			ResetTradeState();
			_pendingReverseToBuy = false;
			_pendingReverseToSell = false;
		}
	}

	private void ApplyProfitLock(decimal step, ICandleMessage candle)
	{
		if (!UseProfitLock || _entryPrice is not decimal entry || _stopLossPrice is not decimal stop)
		return;

		if (_isLongPosition && Position > 0)
		{
			if (!_breakEvenApplied && stop < entry && BreakEvenTriggerPoints > 0m && candle.HighPrice >= entry + step * BreakEvenTriggerPoints)
			{
				_stopLossPrice = entry + step * BreakEvenPoints;
				_breakEvenApplied = true;
			}
		}
		else if (!_isLongPosition && Position < 0)
		{
			if (!_breakEvenApplied && stop > entry && BreakEvenTriggerPoints > 0m && candle.LowPrice <= entry - step * BreakEvenTriggerPoints)
			{
				_stopLossPrice = entry - step * BreakEvenPoints;
				_breakEvenApplied = true;
			}
		}
	}

	private bool TryExitByTargets(ICandleMessage candle)
	{
		if (_entryPrice is null || _stopLossPrice is null || _takeProfitPrice is null)
		return false;

		if (_isLongPosition && Position > 0)
		{
			if (candle.HighPrice >= _takeProfitPrice)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				ResetTradeState();
				return true;
			}

			if (candle.LowPrice <= _stopLossPrice)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				ResetTradeState();
				return true;
			}
		}
		else if (!_isLongPosition && Position < 0)
		{
			if (candle.LowPrice <= _takeProfitPrice)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				ResetTradeState();
				return true;
			}

			if (candle.HighPrice >= _stopLossPrice)
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
				ResetTradeState();
				return true;
			}
		}

		return false;
	}

	private (bool positive, bool negative) EvaluateRsiSignals(decimal currentRsi)
	{
		var positive = false;
		var negative = false;

		if (UseRsiMethodA && _previousRsi is decimal prev)
		{
			if (currentRsi > RsiThreshold && prev < RsiThreshold)
			{
				positive = true;
				negative = false;
			}
			else if (currentRsi < RsiThreshold && prev > RsiThreshold)
			{
				positive = false;
				negative = true;
			}
		}

		if (UseRsiMethodB)
		{
			if (currentRsi > RsiThreshold)
			{
				positive = true;
				negative = false;
			}
			else if (currentRsi < RsiThreshold)
			{
				positive = false;
				negative = true;
			}
		}

		return (positive, negative);
	}

	private void TryEnterPosition(ICandleMessage candle, decimal step, bool buySignal, bool sellSignal)
	{
		if (MaxPositions <= 0)
		return;

		var volume = CalculateEntryVolume();

		if (volume <= 0)
		return;

		if ((_pendingReverseToBuy || buySignal) && Position <= 0)
		{
			var totalVolume = volume + (Position < 0 ? Math.Abs(Position) : 0m);

			if (totalVolume <= 0)
			return;

			BuyMarket();
			InitializeTradeState(candle, step, volume, true);
			_pendingReverseToBuy = false;
			_pendingReverseToSell = false;
		}
		else if ((_pendingReverseToSell || sellSignal) && Position >= 0)
		{
			var totalVolume = volume + (Position > 0 ? Math.Abs(Position) : 0m);

			if (totalVolume <= 0)
			return;

			SellMarket();
			InitializeTradeState(candle, step, volume, false);
			_pendingReverseToBuy = false;
			_pendingReverseToSell = false;
		}
	}

	private decimal CalculateEntryVolume()
	{
		var volume = InitialVolume;

		if (UseMoneyManagement)
		{
			var balance = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;

			if (balance > 0)
			{
				var managed = Math.Ceiling(balance * RiskPercent / 10000m) / 10m;

				if (managed < 0.1m)
				managed = InitialVolume;

				if (managed > 1m)
				managed = Math.Ceiling(managed);

				if (LiveTrading)
				{
					if (IsMiniAccount)
					managed *= 10m;
					else if (managed < 1m)
					managed = 1m;
				}

				if (managed > 100m)
				managed = 100m;

				volume = managed;
			}
		}

		return Math.Max(volume, 0m);
	}

	private void InitializeTradeState(ICandleMessage candle, decimal step, decimal volume, bool isLong)
	{
		_isLongPosition = isLong;
		_entryPrice = candle.ClosePrice;
		_currentVolume = volume;
		_breakEvenApplied = false;
		_barsSinceEntry = 0;
		_lastCandleTime = candle.OpenTime;

		if (isLong)
		{
			_stopLossPrice = _entryPrice - step * StopLossPoints;
			_takeProfitPrice = _entryPrice + step * TakeProfitPoints;
		}
		else
		{
			_stopLossPrice = _entryPrice + step * StopLossPoints;
			_takeProfitPrice = _entryPrice - step * TakeProfitPoints;
		}
	}

	private void ResetTradeState()
	{
		_entryPrice = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_currentVolume = 0m;
		_breakEvenApplied = false;
		_barsSinceEntry = 0;
	}
}