Auf GitHub ansehen

Triple MA Channel Crossover Strategy

Overview

The Triple MA Channel Crossover Strategy trades directional breakouts when a fast moving average moves through both a middle and a slow moving average. A Donchian-style price channel is used to manage exits and to provide optional automatic stop-loss and take-profit levels. The conversion is based on the original MetaTrader "3MACross EA" and keeps its configurable moving average structure, risk controls and trailing logic.

The strategy scales in up to a configurable number of positions, supports manual pip-based risk targets, and can follow the channel for adaptive exits. When enabled, the break-even trigger pushes the stop loss to the entry price plus a safety buffer.

Trading Logic

  • Entry conditions
    • Long: the fast moving average crosses above both the middle and slow averages. If Trade On Close is enabled the cross must occur on a fully closed candle; otherwise the long signal is allowed while the fast average stays above both slower averages.
    • Short: the fast moving average crosses below the middle and slow averages with the same confirmation logic.
    • Existing positions on the opposite side are closed and reversed immediately. Scaling into the same direction is allowed until Max Positions is reached.
  • Exit conditions
    • Price reaching the configured take-profit or channel-based target.
    • Price touching the dynamic stop level (manual distance, trailing stop, break-even movement or channel-based stop).
    • Optional trailing stop adjusts after the price moves in favor by at least the trailing step distance.

Risk Management

  • Stops and targets can be defined manually in pips or derived from the price channel when Auto SL/TP is enabled.
  • Trailing stop and break-even logic mirror the original expert advisor. The stop moves only in the favorable direction and is never relaxed.
  • The Donchian channel provides natural support/resistance bounds that can be used for automatic stop-loss and take-profit placement.
  • Max Positions limits the number of scaling steps, preventing uncontrolled pyramiding.

Key Parameters

Parameter Description
Volume Order size for each scaling step.
Stop Loss (pips) Fixed distance for the protective stop. Set to 0 to disable.
Take Profit (pips) Fixed distance for the profit target. Set to 0 to disable.
Trailing Stop (pips) Distance used by the trailing stop. 0 disables trailing.
Trailing Step (pips) Minimum advance required before updating the trailing stop.
Break Even (pips) Profit required before locking in a break-even stop.
Auto SL/TP Use the Donchian channel instead of fixed distances for stop-loss and take-profit placement.
Trade On Close Require crossovers to be confirmed on a closed candle. If disabled, alignment of averages is checked each bar.
Max Positions Maximum number of scaling steps per direction.
Fast/Middle/Slow MA Period Length of the moving averages.
Fast/Middle/Slow MA Shift Optional shift (in bars) applied to each moving average.
Fast/Middle/Slow MA Type Moving average calculation mode (Simple, Exponential, Smoothed, Weighted).
Channel Period Lookback for the Donchian channel high/low.
Candle Type Timeframe of the candles processed by the strategy.

Implementation Notes

  • Pip distances are converted using Security.PriceStep. For instruments without a valid tick size the strategy falls back to a distance of 1 price unit per pip.
  • Automatic channel management keeps stop-loss and take-profit levels only moving closer to the current price; they are never widened.
  • Break-even activation reuses the trailing step as an additional buffer, matching the original EA behaviour.
  • The strategy is designed for use with StockSharp high-level APIs and handles chart rendering (MAs and Donchian channel) for visual analysis.
  • Ensure historical data depth is sufficient for the slow moving average and channel period so that crossover signals are valid.

Usage

  1. Attach the strategy to a security and set the desired candle timeframe.
  2. Configure moving average periods/methods to match the original EA or your adaptation.
  3. Choose between manual pip-based risk settings or enable automatic channel exits.
  4. Start the strategy; it will subscribe to the configured candles, calculate indicators and trade when the crossover conditions are met.
  5. Monitor the trailing stop and break-even adjustments through the logs and chart overlays.

Disclaimer: Automated trading involves significant risk. Test the strategy thoroughly with historical data and in a simulation environment before deploying to live markets.

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>
/// Triple moving average crossover strategy that uses a Donchian style price channel for risk management.
/// </summary>
public class TripleMaChannelCrossoverStrategy : Strategy
{
	/// <summary>
	/// Moving average calculation modes supported by <see cref="TripleMaChannelCrossoverStrategy"/>.
	/// </summary>
	public enum MovingAverageModes
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed moving average (SMMA).
		/// </summary>
		Smoothed,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Weighted,
	}
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _breakEvenPips;
	private readonly StrategyParam<bool> _useAutoTargets;
	private readonly StrategyParam<bool> _tradeOnClose;
	private readonly StrategyParam<int> _maxPositionCount;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _fastShift;
	private readonly StrategyParam<MovingAverageModes> _fastMaType;
	private readonly StrategyParam<int> _middlePeriod;
	private readonly StrategyParam<int> _middleShift;
	private readonly StrategyParam<MovingAverageModes> _middleMaType;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _slowShift;
	private readonly StrategyParam<MovingAverageModes> _slowMaType;
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private IIndicator _fastMa = null!;
	private IIndicator _middleMa = null!;
	private IIndicator _slowMa = null!;
	private DonchianChannels _channel = null!;

	private decimal _prevFast;
	private decimal _prevMiddle;
	private decimal _prevSlow;
	private bool _hasPreviousValues;

	private decimal _tickSize;

	private decimal? _longStop;
	private decimal? _longTake;
	private decimal _longEntryPrice;
	private bool _longBreakEvenActivated;

	private decimal? _shortStop;
	private decimal? _shortTake;
	private decimal _shortEntryPrice;
	private bool _shortBreakEvenActivated;


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

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

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public int TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Minimal step for trailing stop adjustments in pips.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	/// <summary>
	/// Profit in pips required to move the stop loss to break-even.
	/// </summary>
	public int BreakEvenPips
	{
		get => _breakEvenPips.Value;
		set => _breakEvenPips.Value = value;
	}

	/// <summary>
	/// Enable automatic SL/TP placement based on the price channel.
	/// </summary>
	public bool UseAutoTargets
	{
		get => _useAutoTargets.Value;
		set => _useAutoTargets.Value = value;
	}

	/// <summary>
	/// Trade only when the crossover is confirmed on the closed bar.
	/// </summary>
	public bool TradeOnClose
	{
		get => _tradeOnClose.Value;
		set => _tradeOnClose.Value = value;
	}

	/// <summary>
	/// Maximum number of scaled-in positions.
	/// </summary>
	public int MaxPositionCount
	{
		get => _maxPositionCount.Value;
		set => _maxPositionCount.Value = value;
	}

	/// <summary>
	/// Period for the fast moving average.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Shift for the fast moving average.
	/// </summary>
	public int FastShift
	{
		get => _fastShift.Value;
		set => _fastShift.Value = value;
	}

	/// <summary>
	/// Type of the fast moving average.
	/// </summary>
	public MovingAverageModes FastMaType
	{
		get => _fastMaType.Value;
		set => _fastMaType.Value = value;
	}

	/// <summary>
	/// Period for the middle moving average.
	/// </summary>
	public int MiddlePeriod
	{
		get => _middlePeriod.Value;
		set => _middlePeriod.Value = value;
	}

	/// <summary>
	/// Shift for the middle moving average.
	/// </summary>
	public int MiddleShift
	{
		get => _middleShift.Value;
		set => _middleShift.Value = value;
	}

	/// <summary>
	/// Type of the middle moving average.
	/// </summary>
	public MovingAverageModes MiddleMaType
	{
		get => _middleMaType.Value;
		set => _middleMaType.Value = value;
	}

	/// <summary>
	/// Period for the slow moving average.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Shift for the slow moving average.
	/// </summary>
	public int SlowShift
	{
		get => _slowShift.Value;
		set => _slowShift.Value = value;
	}

	/// <summary>
	/// Type of the slow moving average.
	/// </summary>
	public MovingAverageModes SlowMaType
	{
		get => _slowMaType.Value;
		set => _slowMaType.Value = value;
	}

	/// <summary>
	/// Lookback period for the Donchian price channel.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="TripleMaChannelCrossoverStrategy"/>.
	/// </summary>
	public TripleMaChannelCrossoverStrategy()
	{

		_stopLossPips = Param(nameof(StopLossPips), 0)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 145)
			.SetDisplay("Take Profit (pips)", "Take profit distance", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 0)
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetDisplay("Trailing Step (pips)", "Minimal trailing adjustment", "Risk");

		_breakEvenPips = Param(nameof(BreakEvenPips), 15)
			.SetDisplay("Break Even (pips)", "Profit to move stop to break-even", "Risk");

		_useAutoTargets = Param(nameof(UseAutoTargets), false)
			.SetDisplay("Auto SL/TP", "Use channel for stop & take", "Risk");

		_tradeOnClose = Param(nameof(TradeOnClose), false)
			.SetDisplay("Trade On Close", "Confirm cross on closed bar", "Signals");

		_maxPositionCount = Param(nameof(MaxPositionCount), 5)
			.SetGreaterThanZero()
			.SetDisplay("Max Positions", "Maximum scaling steps", "Trading");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "First moving average", "Moving Averages");

		_fastShift = Param(nameof(FastShift), 0)
			.SetDisplay("Fast MA Shift", "Bars to shift fast MA", "Moving Averages");

		_fastMaType = Param(nameof(FastMaType), MovingAverageModes.Smoothed)
			.SetDisplay("Fast MA Type", "Method for fast MA", "Moving Averages");

		_middlePeriod = Param(nameof(MiddlePeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Middle MA Period", "Second moving average", "Moving Averages");

		_middleShift = Param(nameof(MiddleShift), 0)
			.SetDisplay("Middle MA Shift", "Bars to shift middle MA", "Moving Averages");

		_middleMaType = Param(nameof(MiddleMaType), MovingAverageModes.Smoothed)
			.SetDisplay("Middle MA Type", "Method for middle MA", "Moving Averages");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Third moving average", "Moving Averages");

		_slowShift = Param(nameof(SlowShift), 0)
			.SetDisplay("Slow MA Shift", "Bars to shift slow MA", "Moving Averages");

		_slowMaType = Param(nameof(SlowMaType), MovingAverageModes.Smoothed)
			.SetDisplay("Slow MA Type", "Method for slow MA", "Moving Averages");

		_channelPeriod = Param(nameof(ChannelPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Price channel lookback", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0m;
		_prevMiddle = 0m;
		_prevSlow = 0m;
		_hasPreviousValues = false;
		_longStop = null;
		_longTake = null;
		_longEntryPrice = 0m;
		_longBreakEvenActivated = false;
		_shortStop = null;
		_shortTake = null;
		_shortEntryPrice = 0m;
		_shortBreakEvenActivated = false;
		_tickSize = 0m;
	}

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

		_fastMa = CreateMovingAverage(FastMaType, FastPeriod);
		_middleMa = CreateMovingAverage(MiddleMaType, MiddlePeriod);
		_slowMa = CreateMovingAverage(SlowMaType, SlowPeriod);
		_channel = new DonchianChannels { Length = ChannelPeriod };

		_tickSize = Security.PriceStep ?? 1m;
		if (_tickSize <= 0)
			_tickSize = 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_fastMa, _middleMa, _slowMa, _channel, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _middleMa);
			DrawIndicator(area, _slowMa);
			DrawIndicator(area, _channel);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastVal, IIndicatorValue middleVal, IIndicatorValue slowVal, IIndicatorValue channelVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fastMa.IsFormed || !_middleMa.IsFormed || !_slowMa.IsFormed || !_channel.IsFormed)
			return;

		var fastValue = fastVal.IsEmpty ? 0m : fastVal.GetValue<decimal>();
		var middleValue = middleVal.IsEmpty ? 0m : middleVal.GetValue<decimal>();
		var slowValue = slowVal.IsEmpty ? 0m : slowVal.GetValue<decimal>();

		var channelValue = (DonchianChannelsValue)channelVal;
		var channelUpper = channelValue.UpperBand as decimal?;
		var channelLower = channelValue.LowerBand as decimal?;

		UpdateLongTargets(candle, channelUpper, channelLower);
		UpdateShortTargets(candle, channelUpper, channelLower);
		CheckExits(candle);

		var crossUp = CalculateCrossUp(fastValue, middleValue, slowValue);
		var crossDown = CalculateCrossDown(fastValue, middleValue, slowValue);

		if (crossUp)
		{
			TryEnterLong(candle, channelUpper, channelLower);
		}
		else if (crossDown)
		{
			TryEnterShort(candle, channelUpper, channelLower);
		}

		_prevFast = fastValue;
		_prevMiddle = middleValue;
		_prevSlow = slowValue;
		_hasPreviousValues = true;
	}

	private bool CalculateCrossUp(decimal fastValue, decimal middleValue, decimal slowValue)
	{
		if (TradeOnClose)
		{
			if (!_hasPreviousValues)
				return false;

			var crossMiddle = _prevFast <= _prevMiddle && fastValue > middleValue;
			var crossSlow = _prevFast <= _prevSlow && fastValue > slowValue;
			return crossMiddle && crossSlow;
		}

		return fastValue > middleValue && fastValue > slowValue;
	}

	private bool CalculateCrossDown(decimal fastValue, decimal middleValue, decimal slowValue)
	{
		if (TradeOnClose)
		{
			if (!_hasPreviousValues)
				return false;

			var crossMiddle = _prevFast >= _prevMiddle && fastValue < middleValue;
			var crossSlow = _prevFast >= _prevSlow && fastValue < slowValue;
			return crossMiddle && crossSlow;
		}

		return fastValue < middleValue && fastValue < slowValue;
	}

	private void TryEnterLong(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position >= 0)
		{
			var maxVolume = Volume * MaxPositionCount;
			var currentLong = Position;
			if (currentLong >= maxVolume)
				return;

			var targetVolume = Math.Min(Volume, maxVolume - currentLong);
			if (targetVolume <= 0m)
				return;

			BuyMarket();
		}
		else
		{
			BuyMarket();
			ResetShortState();
		}

		_longEntryPrice = candle.ClosePrice;
		_longBreakEvenActivated = false;
		SetLongTargets(candle, channelUpper, channelLower);
	}

	private void TryEnterShort(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position <= 0)
		{
			var maxVolume = Volume * MaxPositionCount;
			var currentShort = -Position;
			if (currentShort >= maxVolume)
				return;

			var targetVolume = Math.Min(Volume, maxVolume - currentShort);
			if (targetVolume <= 0m)
				return;

			SellMarket();
		}
		else
		{
			SellMarket();
			ResetLongState();
		}

		_shortEntryPrice = candle.ClosePrice;
		_shortBreakEvenActivated = false;
		SetShortTargets(candle, channelUpper, channelLower);
	}

	private void SetLongTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		var entryPrice = candle.ClosePrice;
		var stopDistance = GetDistance(StopLossPips);
		var takeDistance = GetDistance(TakeProfitPips);
		var breakEvenDistance = GetDistance(BreakEvenPips);

		if (UseAutoTargets)
		{
			if (channelLower is decimal lower)
			{
				var candidate = lower;
				if (BreakEvenPips > 0)
					candidate = Math.Max(candidate, entryPrice - breakEvenDistance);
				_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, candidate) : candidate;
			}
			else if (stopDistance > 0m)
			{
				_longStop = entryPrice - stopDistance;
			}

			if (channelUpper is decimal upper)
			{
				var candidate = upper;
				if (BreakEvenPips > 0)
					candidate = Math.Max(candidate, entryPrice + breakEvenDistance);
				_longTake = _longTake.HasValue ? Math.Max(_longTake.Value, candidate) : candidate;
			}
			else if (takeDistance > 0m)
			{
				_longTake = entryPrice + takeDistance;
			}
		}
		else
		{
			_longStop = stopDistance > 0m ? entryPrice - stopDistance : null;
			_longTake = takeDistance > 0m ? entryPrice + takeDistance : null;
		}
	}

	private void SetShortTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		var entryPrice = candle.ClosePrice;
		var stopDistance = GetDistance(StopLossPips);
		var takeDistance = GetDistance(TakeProfitPips);
		var breakEvenDistance = GetDistance(BreakEvenPips);

		if (UseAutoTargets)
		{
			if (channelUpper is decimal upper)
			{
				var candidate = upper;
				if (BreakEvenPips > 0)
					candidate = Math.Min(candidate, entryPrice + breakEvenDistance);
				_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, candidate) : candidate;
			}
			else if (stopDistance > 0m)
			{
				_shortStop = entryPrice + stopDistance;
			}

			if (channelLower is decimal lower)
			{
				var candidate = lower;
				if (BreakEvenPips > 0)
					candidate = Math.Min(candidate, entryPrice - breakEvenDistance);
				_shortTake = _shortTake.HasValue ? Math.Min(_shortTake.Value, candidate) : candidate;
			}
			else if (takeDistance > 0m)
			{
				_shortTake = entryPrice - takeDistance;
			}
		}
		else
		{
			_shortStop = stopDistance > 0m ? entryPrice + stopDistance : null;
			_shortTake = takeDistance > 0m ? entryPrice - takeDistance : null;
		}
	}

	private void UpdateLongTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position <= 0)
		{
			ResetLongState();
			return;
		}

		var breakEvenDistance = GetDistance(BreakEvenPips);
		var trailingDistance = GetDistance(TrailingStopPips);
		var trailingStep = GetDistance(TrailingStepPips);
		var entryPrice = _longEntryPrice;

		if (UseAutoTargets && channelLower is decimal lower)
		{
			var candidate = lower;
			if (BreakEvenPips > 0)
				candidate = Math.Max(candidate, entryPrice - breakEvenDistance);
			_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, candidate) : candidate;
		}

		if (UseAutoTargets && channelUpper is decimal upper)
		{
			var candidate = upper;
			if (BreakEvenPips > 0)
				candidate = Math.Max(candidate, entryPrice + breakEvenDistance);
			_longTake = _longTake.HasValue ? Math.Max(_longTake.Value, candidate) : candidate;
		}

		if (trailingDistance > 0m)
		{
			var candidate = candle.ClosePrice - trailingDistance;
			if (_longStop is decimal currentStop)
			{
				if (candidate - currentStop >= Math.Max(trailingStep, _tickSize))
					_longStop = candidate;
			}
			else
			{
				_longStop = candidate;
			}
		}

		if (BreakEvenPips > 0 && !_longBreakEvenActivated)
		{
			var activationPrice = entryPrice + breakEvenDistance + Math.Max(0m, trailingStep);
			var targetStop = entryPrice + breakEvenDistance;
			if (candle.ClosePrice >= activationPrice)
			{
				_longBreakEvenActivated = true;
				_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, targetStop) : targetStop;
			}
		}
	}

	private void UpdateShortTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position >= 0)
		{
			ResetShortState();
			return;
		}

		var breakEvenDistance = GetDistance(BreakEvenPips);
		var trailingDistance = GetDistance(TrailingStopPips);
		var trailingStep = GetDistance(TrailingStepPips);
		var entryPrice = _shortEntryPrice;

		if (UseAutoTargets && channelUpper is decimal upper)
		{
			var candidate = upper;
			if (BreakEvenPips > 0)
				candidate = Math.Min(candidate, entryPrice + breakEvenDistance);
			_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, candidate) : candidate;
		}

		if (UseAutoTargets && channelLower is decimal lower)
		{
			var candidate = lower;
			if (BreakEvenPips > 0)
				candidate = Math.Min(candidate, entryPrice - breakEvenDistance);
			_shortTake = _shortTake.HasValue ? Math.Min(_shortTake.Value, candidate) : candidate;
		}

		if (trailingDistance > 0m)
		{
			var candidate = candle.ClosePrice + trailingDistance;
			if (_shortStop is decimal currentStop)
			{
				if (currentStop - candidate >= Math.Max(trailingStep, _tickSize))
					_shortStop = candidate;
			}
			else
			{
				_shortStop = candidate;
			}
		}

		if (BreakEvenPips > 0 && !_shortBreakEvenActivated)
		{
			var activationPrice = entryPrice - breakEvenDistance - Math.Max(0m, trailingStep);
			var targetStop = entryPrice - breakEvenDistance;
			if (candle.ClosePrice <= activationPrice)
			{
				_shortBreakEvenActivated = true;
				_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, targetStop) : targetStop;
			}
		}
	}

	private void CheckExits(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longTake is decimal take && candle.HighPrice >= take)
			{
				SellMarket();
				ResetLongState();
			}
			else if (_longStop is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
				ResetLongState();
			}
		}
		else if (Position < 0)
		{
			if (_shortTake is decimal take && candle.LowPrice <= take)
			{
				BuyMarket();
				ResetShortState();
			}
			else if (_shortStop is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
				ResetShortState();
			}
		}
	}

	private decimal GetDistance(int pips)
	{
		return pips <= 0 ? 0m : pips * _tickSize;
	}

	private void ResetLongState()
	{
		_longStop = null;
		_longTake = null;
		_longEntryPrice = 0m;
		_longBreakEvenActivated = false;
	}

	private void ResetShortState()
	{
		_shortStop = null;
		_shortTake = null;
		_shortEntryPrice = 0m;
		_shortBreakEvenActivated = false;
	}

	private IIndicator CreateMovingAverage(MovingAverageModes mode, int length)
	{
		return mode switch
		{
			MovingAverageModes.Exponential => new ExponentialMovingAverage { Length = length },
			MovingAverageModes.Weighted => new WeightedMovingAverage { Length = length },
			MovingAverageModes.Smoothed => new SmoothedMovingAverage { Length = length },
			_ => new SimpleMovingAverage { Length = length },
		};
	}
}