Ver no GitHub

Estratégia de Cruzamento de Canal de Triple MA

Visão Geral

A Estratégia de Cruzamento de Canal de Triple MA negocia rompimentos direcionais quando uma média móvel rápida se move através de uma média móvel intermediária e uma lenta. Um canal de preço no estilo Donchian é usado para gerenciar saídas e fornecer níveis opcionais automáticos de stop-loss e take-profit. A conversão é baseada no "3MACross EA" original do MetaTrader e mantém sua estrutura de média móvel configurável, controles de risco e lógica de trailing.

A estratégia escala até um número configurável de posições, suporta alvos de risco manuais baseados em pips e pode seguir o canal para saídas adaptativas. Quando habilitado, o gatilho de break-even empurra o stop loss para o preço de entrada mais um buffer de segurança.

Lógica de Trading

  • Critérios de entrada
    • Comprado: a média móvel rápida cruza acima de ambas as médias intermediária e lenta. Se Trade On Close estiver habilitado, o cruzamento deve ocorrer em uma vela completamente fechada; caso contrário, o sinal de compra é permitido enquanto a média rápida permanecer acima de ambas as médias mais lentas.
    • Vendido: a média móvel rápida cruza abaixo das médias intermediária e lenta com a mesma lógica de confirmação.
    • As posições existentes no lado oposto são fechadas e revertidas imediatamente. O escalonamento na mesma direção é permitido até que Max Positions seja atingido.
  • Critérios de saída
    • Preço atingindo o take-profit configurado ou o alvo baseado em canal.
    • Preço tocando o nível de stop dinâmico (distância manual, trailing stop, movimento de break-even ou stop baseado em canal).
    • O trailing stop opcional ajusta depois que o preço se move a favor por pelo menos a distância do passo de trailing.

Gestão de Risco

  • Stops e alvos podem ser definidos manualmente em pips ou derivados do canal de preço quando Auto SL/TP está habilitado.
  • A lógica de trailing stop e break-even refletem o consultor especializado original. O stop se move apenas na direção favorável e nunca é relaxado.
  • O canal Donchian fornece limites naturais de suporte/resistência que podem ser usados para colocação automática de stop-loss e take-profit.
  • Max Positions limita o número de passos de escalonamento, prevenindo piramidação descontrolada.

Parâmetros Chave

Parâmetro Descrição
Volume Tamanho de ordem para cada passo de escalonamento.
Stop Loss (pips) Distância fixa para o stop protetor. Definir como 0 para desabilitar.
Take Profit (pips) Distância fixa para o alvo de lucro. Definir como 0 para desabilitar.
Trailing Stop (pips) Distância usada pelo trailing stop. 0 desabilita o trailing.
Trailing Step (pips) Avanço mínimo necessário antes de atualizar o trailing stop.
Break Even (pips) Lucro necessário antes de fixar um stop de break-even.
Auto SL/TP Usar o canal Donchian em vez de distâncias fixas para colocação de stop-loss e take-profit.
Trade On Close Exigir que os cruzamentos sejam confirmados em uma vela fechada. Se desabilitado, o alinhamento das médias é verificado a cada barra.
Max Positions Número máximo de passos de escalonamento por direção.
Fast/Middle/Slow MA Period Comprimento das médias móveis.
Fast/Middle/Slow MA Shift Deslocamento opcional (em barras) aplicado a cada média móvel.
Fast/Middle/Slow MA Type Modo de cálculo da média móvel (Simples, Exponencial, Suavizada, Ponderada).
Channel Period Lookback para o máximo/mínimo do canal Donchian.
Candle Type Período das velas processadas pela estratégia.

Notas de Implementação

  • As distâncias em pips são convertidas usando Security.PriceStep. Para instrumentos sem tamanho de tick válido, a estratégia recorre a uma distância de 1 unidade de preço por pip.
  • O gerenciamento automático de canal mantém os níveis de stop-loss e take-profit se movendo apenas mais próximos do preço atual; eles nunca são ampliados.
  • A ativação de break-even reutiliza o passo de trailing como um buffer adicional, correspondendo ao comportamento original do EA.
  • A estratégia é projetada para uso com as APIs de alto nível do StockSharp e lida com a renderização de gráficos (MAs e canal Donchian) para análise visual.
  • Certifique-se de que a profundidade de dados históricos seja suficiente para a média móvel lenta e o período do canal para que os sinais de cruzamento sejam válidos.

Uso

  1. Anexar a estratégia a um instrumento e definir o período de velas desejado.
  2. Configurar os períodos/métodos das médias móveis para corresponder ao EA original ou sua adaptação.
  3. Escolher entre configurações de risco manuais baseadas em pips ou habilitar saídas automáticas de canal.
  4. Iniciar a estratégia; ela se subscreverá às velas configuradas, calculará indicadores e negociará quando as condições de cruzamento forem atendidas.
  5. Monitorar o trailing stop e os ajustes de break-even através dos logs e sobreposições de gráfico.

Aviso: O trading automatizado envolve riscos significativos. Teste a estratégia completamente com dados históricos e em um ambiente de simulação antes de implantar em mercados ao vivo.

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 },
		};
	}
}