Открыть на GitHub

Стратегия Triple MA Channel Crossover

Обзор

Triple MA Channel Crossover торгует направленные пробои в момент, когда быстрая скользящая средняя пересекает сверху или снизу две более медленные средние. Для сопровождения позиции используется ценовой канал Дончиана, который может автоматически задавать уровни стоп-лосса и тейк-профита. Алгоритм является переносом оригинального советника MetaTrader «3MACross EA» и сохраняет его настраиваемые параметры скользящих средних и риск-менеджмента.

Стратегия поддерживает поэтапное наращивание позиции, работу с фиксированными целями в пунктах и динамическое сопровождение по каналу. При активации функции безубыточности стоп переносится к цене входа с дополнительным зазором безопасности.

Торговая логика

  • Вход в лонг — быстрая средняя пересекает вверх среднюю и медленную. При включенном параметре Trade On Close пересечение должно быть подтверждено на закрытом баре; при выключенном параметре достаточно, чтобы быстрая средняя находилась выше обеих остальных.
  • Вход в шорт — зеркальное условие: быстрая средняя пересекает вниз две медленные средние.
  • При появлении сигнала противоположной направленности стратегия закрывает текущую позицию и при необходимости сразу переворачива ется. Дополнительные входы в ту же сторону разрешены, пока общее число лотов не превысит Max Positions.

Управление рисками

  • Стоп-лосс и тейк-профит можно задать фиксированным расстоянием в пунктах или поручить их расчёт ценовому каналу (Auto SL/TP).
  • Трейлинг-стоп и перенос в безубыток повторяют логику исходного советника: защита только ужесточается и никогда не ослабляется.
  • Канал Дончиана выступает динамической зоной поддержки/сопротивления, позволяя быстро адаптировать выходы.
  • Параметр Max Positions ограничивает количество доливок и предотвращает чрезмерное наращивание позиции.

Основные параметры

Параметр Описание
Volume Объём заявки для каждой доливки.
Stop Loss (pips) Фиксированное расстояние стоп-лосса (0 — выключено).
Take Profit (pips) Фиксированное расстояние тейк-профита (0 — выключено).
Trailing Stop (pips) Дистанция трейлинг-стопа (0 — выключено).
Trailing Step (pips) Минимальный шаг обновления трейлинг-стопа.
Break Even (pips) Прибыль в пунктах, после которой стоп переносится в безубыток.
Auto SL/TP Использовать канал Дончиана для расчёта стопов и целей.
Trade On Close Требовать подтверждения пересечения на закрытом баре (иначе проверяется каждое обновление свечи).
Max Positions Максимальное число доливок в одном направлении.
Fast/Middle/Slow MA Period Периоды трёх скользящих средних.
Fast/Middle/Slow MA Shift Сдвиг соответствующей скользящей (в барах).
Fast/Middle/Slow MA Type Типы скользящих (Simple, Exponential, Smoothed, Weighted).
Channel Period Глубина расчёта канала Дончиана.
Candle Type Таймфрейм свечей, которые обрабатывает стратегия.

Особенности реализации

  • Перевод пунктов в цены выполняется через Security.PriceStep. При отсутствии корректного шага цены используется значение 1.
  • Автоматический канал никогда не отдаляет стоп или цель от цены — уровни двигаются только в сторону сокращения риска.
  • Порог безубыточности использует «шаг трейлинга» как дополнительный буфер, что соответствует логике оригинального советника.
  • Стратегия построена на высокоуровневом API StockSharp и может отображать на графике свечи, скользящие средние и канал.
  • Для корректной работы убедитесь, что в истории достаточно данных для формирования самой медленной средней и канала.

Порядок использования

  1. Привяжите стратегию к нужному инструменту и выберите таймфрейм свечей;
  2. Настройте периоды, типы и сдвиги скользящих средних под свои требования или под параметры оригинального советника;
  3. Выберите режим управления риском: фиксированные цели или автоматические уровни по каналу;
  4. Запустите стратегию — она подпишется на свечи, рассчитает индикаторы и начнёт торговать при появлении сигналов;
  5. Следите за перемещениями стопов и результатами сделок через журнал и построенные графики.

Важно: автоматическая торговля связана с повышенным риском. Перед использованием на реальном счёте обязательно протестируйте стратегию на истории и в демо-среде.

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