Открыть на GitHub

Стратегия Crossing of Two iMA v2

Обзор

Эта стратегия переносит советник MetaTrader «Crossing of two iMA v2» на высокоуровневый API StockSharp. Сигналы возникают при пересечении двух смещённых скользящих средних, а при необходимости направление дополнительно подтверждается третьей средней. Встроенные стоп-приказы, фиксированное или процентное управление объёмом и ступенчатое трейлинг-сопровождение сделок воспроизводят поведение оригинального робота и остаются совместимыми с правилами разработки StockSharp.

Индикаторы и входные данные

  • Первая скользящая средняя – настраиваемые период, сдвиг, метод сглаживания и тип цены.
  • Вторая скользящая средняя – отдельный набор параметров, позволяющий задать другую конфигурацию.
  • Фильтр третьей скользящей средней – опциональный трендовый фильтр; лонги разрешаются только когда первая средняя ниже фильтра, шорты – когда выше фильтра.
  • Тип свечей – указывает таймфрейм/источник данных для подписки на свечи.

Логика торговли

Шаг 1 – мгновенное пересечение

  1. После закрытия каждой свечи стратегия обновляет значения всех скользящих средних в соответствии с выбранными типами цен.
  2. Лонг открывается, если первая средняя пересекает вторую снизу вверх между предыдущей и текущей свечой.
  3. Шорт открывается, если первая средняя пересекает вторую сверху вниз между предыдущей и текущей свечой.
  4. При активном фильтре для лонга требуется, чтобы первая средняя оставалась ниже фильтра; для шорта – чтобы первая средняя была выше фильтра.

Шаг 2 – подтверждение с задержкой

Если на Шаге 1 сигнал не сформировался, стратегия проверяет, не произошло ли пересечение двумя барами ранее и остаётся ли оно актуальным. Это повторяет логику оригинального советника, который анализирует недавнюю историю. Для предотвращения повторных входов сигнал допускается только спустя минимум три свечи после последней сделки.

Исполнение сделок

  • Входы выполняются по рыночной цене. Противоположные позиции закрываются перед открытием в новом направлении.
  • Выход происходит при достижении стоп-лосса, тейк-профита или трейлинг-стопа. Как только цена касается защитного уровня на текущей свече, позиция закрывается рыночной заявкой.

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

  • Стоп-лосс и тейк-профит задаются в пипсах. Для получения ценового смещения значения умножаются на PriceStep инструмента (при отсутствии шага используется значение 1).
  • Трейлинг-стоп стартует от цены входа и сопровождает позицию при движении в прибыль. Новый уровень фиксируется, когда лучшая цена улучшилась минимум на TrailingStepPips пипсов относительно предыдущего уровня.
  • Если одновременно активны обычный стоп и трейлинг, стратегия выбирает более консервативный уровень (выше для лонга, ниже для шорта).

Управление объёмом

  • При UseRiskPercent = true объём рассчитывается как Equity * RiskPercent / (StopLossPips * PipValue). При отсутствии стопа используется резервный фиксированный объём.
  • При UseRiskPercent = false всегда применяется значение FixedVolume.
  • Параметр PipValue должен отражать денежную стоимость одного пипса на один лот/контракт выбранного инструмента.

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

  • Реализация работает только по закрытым свечам и не регистрирует отложенные заявки. При необходимости лимитных/стоповых входов стратегию можно расширить.
  • Фильтр третьей средней можно отключить, чтобы торговать каждое пересечение (аналог InpFilterMA = false в исходном роботе).
  • Для корректного управления рисками проверьте соответствие типа свечей, шага цены и стоимости пипса торгуемому инструменту.

Параметры

Название Описание Значение по умолчанию
FirstPeriod Период первой скользящей средней. 5
FirstShift Сдвиг (в барах) выходного значения первой средней. 3
FirstMethod Метод сглаживания первой средней (Simple, Exponential, Smoothed, Weighted). Smoothed
FirstAppliedPrice Тип цены для расчёта первой средней (Close, Open, High, Low, Median, Typical, Weighted). Close
SecondPeriod Период второй скользящей средней. 8
SecondShift Сдвиг (в барах) выходного значения второй средней. 5
SecondMethod Метод сглаживания второй средней. Smoothed
SecondAppliedPrice Тип цены для расчёта второй средней. Close
UseFilter Включает фильтр третьей скользящей средней. true
ThirdPeriod Период фильтрующей скользящей средней. 13
ThirdShift Сдвиг (в барах) фильтрующей средней. 8
ThirdMethod Метод сглаживания фильтра. Smoothed
ThirdAppliedPrice Тип цены для фильтра. Close
UseRiskPercent Переключение между фиксированным объёмом и риском в процентах. true
FixedVolume Объём сделки при фиксированном управлении. 0.1
RiskPercent Доля капитала, которую допускается рискнуть в сделке. 5
PipValue Денежная стоимость одного пипса для одного лота/контракта. 1
StopLossPips Размер стоп-лосса в пипсах. 50
TakeProfitPips Размер тейк-профита в пипсах. 50
TrailingStopPips Размер трейлинг-стопа в пипсах. 10
TrailingStepPips Минимальный шаг, на который должен сдвинуться трейлинг-стоп. 4
CandleType Тип свечей/таймфрейм, используемый стратегией. Свечи 1 минуты
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>
/// Crossing of two iMA strategy with optional third filter and trailing protection.
/// </summary>
public class CrossingOfTwoIMaV2Strategy : Strategy
{
	private readonly StrategyParam<int> _firstPeriod;
	private readonly StrategyParam<int> _firstShift;
	private readonly StrategyParam<MaMethods> _firstMethod;
	private readonly StrategyParam<AppliedPriceTypes> _firstPrice;
	private readonly StrategyParam<int> _secondPeriod;
	private readonly StrategyParam<int> _secondShift;
	private readonly StrategyParam<MaMethods> _secondMethod;
	private readonly StrategyParam<AppliedPriceTypes> _secondPrice;
	private readonly StrategyParam<bool> _useFilter;
	private readonly StrategyParam<int> _thirdPeriod;
	private readonly StrategyParam<int> _thirdShift;
	private readonly StrategyParam<MaMethods> _thirdMethod;
	private readonly StrategyParam<AppliedPriceTypes> _thirdPrice;
	private readonly StrategyParam<bool> _useRiskPercent;
	private readonly StrategyParam<decimal> _fixedVolume;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<decimal> _pipValue;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<DataType> _candleType;

	private IIndicator _firstMa;
	private IIndicator _secondMa;
	private IIndicator _thirdMa;

	private decimal?[] _firstSeries = Array.Empty<decimal?>();
	private decimal?[] _secondSeries = Array.Empty<decimal?>();
	private decimal?[] _thirdSeries = Array.Empty<decimal?>();

	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakeProfit;
	private decimal? _shortTakeProfit;
	private decimal? _longTrail;
	private decimal? _shortTrail;
	private decimal? _bestLongPrice;
	private decimal? _bestShortPrice;
	private int _barsSinceLastEntry;

	/// <summary>
	/// Period of the first moving average.
	/// </summary>
	public int FirstPeriod
	{
		get => _firstPeriod.Value;
		set => _firstPeriod.Value = value;
	}

	/// <summary>
	/// Shift of the first moving average in bars.
	/// </summary>
	public int FirstShift
	{
		get => _firstShift.Value;
		set => _firstShift.Value = value;
	}

	/// <summary>
	/// Smoothing method for the first moving average.
	/// </summary>
	public MaMethods FirstMethod
	{
		get => _firstMethod.Value;
		set => _firstMethod.Value = value;
	}

	/// <summary>
	/// Price source for the first moving average.
	/// </summary>
	public AppliedPriceTypes FirstAppliedPrice
	{
		get => _firstPrice.Value;
		set => _firstPrice.Value = value;
	}

	/// <summary>
	/// Period of the second moving average.
	/// </summary>
	public int SecondPeriod
	{
		get => _secondPeriod.Value;
		set => _secondPeriod.Value = value;
	}

	/// <summary>
	/// Shift of the second moving average in bars.
	/// </summary>
	public int SecondShift
	{
		get => _secondShift.Value;
		set => _secondShift.Value = value;
	}

	/// <summary>
	/// Smoothing method for the second moving average.
	/// </summary>
	public MaMethods SecondMethod
	{
		get => _secondMethod.Value;
		set => _secondMethod.Value = value;
	}

	/// <summary>
	/// Price source for the second moving average.
	/// </summary>
	public AppliedPriceTypes SecondAppliedPrice
	{
		get => _secondPrice.Value;
		set => _secondPrice.Value = value;
	}

	/// <summary>
	/// Enable the third moving average filter.
	/// </summary>
	public bool UseFilter
	{
		get => _useFilter.Value;
		set => _useFilter.Value = value;
	}

	/// <summary>
	/// Period of the third moving average filter.
	/// </summary>
	public int ThirdPeriod
	{
		get => _thirdPeriod.Value;
		set => _thirdPeriod.Value = value;
	}

	/// <summary>
	/// Shift of the third moving average filter in bars.
	/// </summary>
	public int ThirdShift
	{
		get => _thirdShift.Value;
		set => _thirdShift.Value = value;
	}

	/// <summary>
	/// Smoothing method for the third moving average filter.
	/// </summary>
	public MaMethods ThirdMethod
	{
		get => _thirdMethod.Value;
		set => _thirdMethod.Value = value;
	}

	/// <summary>
	/// Price source for the third moving average filter.
	/// </summary>
	public AppliedPriceTypes ThirdAppliedPrice
	{
		get => _thirdPrice.Value;
		set => _thirdPrice.Value = value;
	}

	/// <summary>
	/// Use risk percentage position sizing instead of fixed volume.
	/// </summary>
	public bool UseRiskPercent
	{
		get => _useRiskPercent.Value;
		set => _useRiskPercent.Value = value;
	}

	/// <summary>
	/// Fixed volume when risk percentage sizing is disabled.
	/// </summary>
	public decimal FixedVolume
	{
		get => _fixedVolume.Value;
		set => _fixedVolume.Value = value;
	}

	/// <summary>
	/// Percentage of equity risked per trade when risk sizing is enabled.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	/// <summary>
	/// Monetary value of one pip for a single lot.
	/// </summary>
	public decimal PipValue
	{
		get => _pipValue.Value;
		set => _pipValue.Value = value;
	}

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

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

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

	/// <summary>
	/// Minimum trailing adjustment step in pips.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="CrossingOfTwoIMaV2Strategy"/>.
	/// </summary>
	public CrossingOfTwoIMaV2Strategy()
	{
		_firstPeriod = Param(nameof(FirstPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("First MA Period", "Period of the first moving average", "First Moving Average")
		;

		_firstShift = Param(nameof(FirstShift), 0)
		.SetNotNegative()
		.SetDisplay("First MA Shift", "Shift (in bars) applied to the first moving average", "First Moving Average")
		;

		_firstMethod = Param(nameof(FirstMethod), MaMethods.Simple)
		.SetDisplay("First MA Method", "Smoothing method for the first moving average", "First Moving Average")
		;

		_firstPrice = Param(nameof(FirstAppliedPrice), AppliedPriceTypes.Close)
		.SetDisplay("First MA Price", "Price source for the first moving average", "First Moving Average");

		_secondPeriod = Param(nameof(SecondPeriod), 8)
		.SetGreaterThanZero()
		.SetDisplay("Second MA Period", "Period of the second moving average", "Second Moving Average")
		;

		_secondShift = Param(nameof(SecondShift), 0)
		.SetNotNegative()
		.SetDisplay("Second MA Shift", "Shift (in bars) applied to the second moving average", "Second Moving Average")
		;

		_secondMethod = Param(nameof(SecondMethod), MaMethods.Simple)
		.SetDisplay("Second MA Method", "Smoothing method for the second moving average", "Second Moving Average")
		;

		_secondPrice = Param(nameof(SecondAppliedPrice), AppliedPriceTypes.Close)
		.SetDisplay("Second MA Price", "Price source for the second moving average", "Second Moving Average");

		_useFilter = Param(nameof(UseFilter), false)
		.SetDisplay("Enable Filter", "Use the third moving average as a directional filter", "Filter");

		_thirdPeriod = Param(nameof(ThirdPeriod), 13)
		.SetGreaterThanZero()
		.SetDisplay("Third MA Period", "Period of the third moving average filter", "Filter")
		;

		_thirdShift = Param(nameof(ThirdShift), 0)
		.SetNotNegative()
		.SetDisplay("Third MA Shift", "Shift (in bars) applied to the third moving average filter", "Filter")
		;

		_thirdMethod = Param(nameof(ThirdMethod), MaMethods.Simple)
		.SetDisplay("Third MA Method", "Smoothing method for the third moving average filter", "Filter")
		;

		_thirdPrice = Param(nameof(ThirdAppliedPrice), AppliedPriceTypes.Close)
		.SetDisplay("Third MA Price", "Price source for the third moving average filter", "Filter");

		_useRiskPercent = Param(nameof(UseRiskPercent), true)
		.SetDisplay("Risk Based Sizing", "Use percentage risk position sizing", "Risk");

		_fixedVolume = Param(nameof(FixedVolume), 0.1m)
		.SetGreaterThanZero()
		.SetDisplay("Fixed Volume", "Trade volume when fixed sizing is enabled", "Risk");

		_riskPercent = Param(nameof(RiskPercent), 5m)
		.SetGreaterThanZero()
		.SetDisplay("Risk Percent", "Percentage of equity risked per trade", "Risk")
		;

		_pipValue = Param(nameof(PipValue), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Pip Value", "Monetary value of one pip for a single lot", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 50)
		.SetNotNegative()
		.SetDisplay("Stop Loss", "Stop loss distance in pips", "Protection");

		_takeProfitPips = Param(nameof(TakeProfitPips), 50)
		.SetNotNegative()
		.SetDisplay("Take Profit", "Take profit distance in pips", "Protection");

		_trailingStopPips = Param(nameof(TrailingStopPips), 10)
		.SetNotNegative()
		.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Protection");

		_trailingStepPips = Param(nameof(TrailingStepPips), 4)
		.SetNotNegative()
		.SetDisplay("Trailing Step", "Minimum trailing stop adjustment in pips", "Protection");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
		.SetDisplay("Candle Type", "Candle type used for analysis", "General");

		_barsSinceLastEntry = int.MaxValue;
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_firstMa = null;
		_secondMa = null;
		_thirdMa = null;
		_firstSeries = Array.Empty<decimal?>();
		_secondSeries = Array.Empty<decimal?>();
		_thirdSeries = Array.Empty<decimal?>();
		ResetTradeState();
		_barsSinceLastEntry = int.MaxValue;
	}

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

		_firstMa = CreateMovingAverage(FirstMethod, FirstPeriod);
		_secondMa = CreateMovingAverage(SecondMethod, SecondPeriod);
		_thirdMa = UseFilter ? CreateMovingAverage(ThirdMethod, ThirdPeriod) : null;

		_firstSeries = new decimal?[FirstShift + 3];
		_secondSeries = new decimal?[SecondShift + 3];
		_thirdSeries = UseFilter ? new decimal?[ThirdShift + 1] : Array.Empty<decimal?>();

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		// Ignore unfinished candles to work on closed bars only.
		if (candle.State != CandleStates.Finished)
		return;

		if (_barsSinceLastEntry < int.MaxValue)
		_barsSinceLastEntry++;

		// Manage open positions and exit if protections trigger.
		if (UpdateRiskManagement(candle))
		return;

		var firstInput = GetAppliedPrice(candle, FirstAppliedPrice);
		var secondInput = GetAppliedPrice(candle, SecondAppliedPrice);
		var thirdInput = UseFilter ? GetAppliedPrice(candle, ThirdAppliedPrice) : (decimal?)null;

		// Update indicator series with the latest values.
		var firstValue = _firstMa?.Process(firstInput, candle.OpenTime, true);
		ShiftSeries(_firstSeries, firstValue?.IsFinal == true ? firstValue.ToDecimal() : (decimal?)null);

		var secondValue = _secondMa?.Process(secondInput, candle.OpenTime, true);
		ShiftSeries(_secondSeries, secondValue?.IsFinal == true ? secondValue.ToDecimal() : (decimal?)null);

		if (UseFilter && _thirdMa != null && _thirdSeries.Length > 0 && thirdInput.HasValue)
		{
			var thirdValue = _thirdMa.Process(new DecimalIndicatorValue(_thirdMa, thirdInput.Value, candle.OpenTime));
			ShiftSeries(_thirdSeries, thirdValue.IsFinal ? thirdValue.ToDecimal() : (decimal?)null);
		}

		// Ensure we have enough data for crossover evaluation.
		if (!HasSeriesValue(_firstSeries, FirstShift, 2) || !HasSeriesValue(_secondSeries, SecondShift, 2))
		return;

		var first0 = GetSeriesValue(_firstSeries, FirstShift, 0)!.Value;
		var first1 = GetSeriesValue(_firstSeries, FirstShift, 1)!.Value;
		var second0 = GetSeriesValue(_secondSeries, SecondShift, 0)!.Value;
		var second1 = GetSeriesValue(_secondSeries, SecondShift, 1)!.Value;

		var buySignal = first0 > second0 && first1 < second1;
		var sellSignal = first0 < second0 && first1 > second1;

		if (!buySignal && !sellSignal && HasSeriesValue(_firstSeries, FirstShift, 2) && HasSeriesValue(_secondSeries, SecondShift, 2))
		{
			var first2 = GetSeriesValue(_firstSeries, FirstShift, 2)!.Value;
			var second2 = GetSeriesValue(_secondSeries, SecondShift, 2)!.Value;

			if (first0 > second0 && first2 < second2 && _barsSinceLastEntry > 2)
			{
				buySignal = true;
			}
			else if (first0 < second0 && first2 > second2 && _barsSinceLastEntry > 2)
			{
				sellSignal = true;
			}
		}

		if (UseFilter)
		{
			if (_thirdSeries.Length > 0 && HasSeriesValue(_thirdSeries, ThirdShift, 0))
			{
				var filterValue = GetSeriesValue(_thirdSeries, ThirdShift, 0)!.Value;
				if (buySignal && filterValue >= first0)
				buySignal = false;
				if (sellSignal && filterValue <= first0)
				sellSignal = false;
			}
			else if (buySignal || sellSignal)
			{
				return;
			}
		}

		if (buySignal && Position <= 0)
		{
			EnterLong(candle);
		}
		else if (sellSignal && Position >= 0)
		{
			EnterShort(candle);
		}
	}

	private void EnterLong(ICandleMessage candle)
	{
		var volume = GetEntryVolume();
		if (volume <= 0m)
		return;

		if (Position < 0)
		{
			BuyMarket(Math.Abs(Position));
		}

		BuyMarket(volume);
		SetLongProtection(candle.ClosePrice);
		_barsSinceLastEntry = 0;
	}

	private void EnterShort(ICandleMessage candle)
	{
		var volume = GetEntryVolume();
		if (volume <= 0m)
		return;

		if (Position > 0)
		{
			SellMarket(Math.Abs(Position));
		}

		SellMarket(volume);
		SetShortProtection(candle.ClosePrice);
		_barsSinceLastEntry = 0;
	}

	private decimal GetEntryVolume()
	{
		if (!UseRiskPercent)
		return FixedVolume;

		var equity = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
		if (equity <= 0m)
		return FixedVolume;

		var riskAmount = equity * RiskPercent / 100m;
		var riskPips = StopLossPips > 0 ? StopLossPips : TrailingStopPips;
		if (riskPips <= 0)
		return FixedVolume;

		if (PipValue <= 0m)
		return FixedVolume;

		var volume = riskAmount / (riskPips * PipValue);
		return volume > 0m ? volume : FixedVolume;
	}

	private bool UpdateRiskManagement(ICandleMessage candle)
	{
		var point = GetPointValue();
		var trailingStep = TrailingStepPips > 0 ? TrailingStepPips * point : 0m;

		if (Position > 0)
		{
			_bestLongPrice = _bestLongPrice.HasValue ? Math.Max(_bestLongPrice.Value, candle.HighPrice) : candle.HighPrice;

			if (TrailingStopPips > 0 && _bestLongPrice.HasValue)
			{
				var desiredStop = _bestLongPrice.Value - TrailingStopPips * point;
				if (!_longTrail.HasValue || desiredStop - _longTrail.Value >= trailingStep)
				_longTrail = desiredStop;
			}

			var exitStop = CombineLongStops(_longStopPrice, _longTrail);
			if (exitStop.HasValue && candle.LowPrice <= exitStop.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetTradeState();
				return true;
			}

			if (_longTakeProfit.HasValue && candle.HighPrice >= _longTakeProfit.Value)
			{
				SellMarket(Math.Abs(Position));
				ResetTradeState();
				return true;
			}
		}
		else if (Position < 0)
		{
			_bestShortPrice = _bestShortPrice.HasValue ? Math.Min(_bestShortPrice.Value, candle.LowPrice) : candle.LowPrice;

			if (TrailingStopPips > 0 && _bestShortPrice.HasValue)
			{
				var desiredStop = _bestShortPrice.Value + TrailingStopPips * point;
				if (!_shortTrail.HasValue || _shortTrail.Value - desiredStop >= trailingStep)
				_shortTrail = desiredStop;
			}

			var exitStop = CombineShortStops(_shortStopPrice, _shortTrail);
			if (exitStop.HasValue && candle.HighPrice >= exitStop.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetTradeState();
				return true;
			}

			if (_shortTakeProfit.HasValue && candle.LowPrice <= _shortTakeProfit.Value)
			{
				BuyMarket(Math.Abs(Position));
				ResetTradeState();
				return true;
			}
		}
		else
		{
			ResetTradeState();
		}

		return false;
	}

	private void SetLongProtection(decimal entryPrice)
	{
		var point = GetPointValue();
		_longStopPrice = StopLossPips > 0 ? entryPrice - StopLossPips * point : null;
		_longTakeProfit = TakeProfitPips > 0 ? entryPrice + TakeProfitPips * point : null;
		_longTrail = TrailingStopPips > 0 ? entryPrice - TrailingStopPips * point : null;
		_bestLongPrice = entryPrice;
		_shortStopPrice = null;
		_shortTakeProfit = null;
		_shortTrail = null;
		_bestShortPrice = null;
	}

	private void SetShortProtection(decimal entryPrice)
	{
		var point = GetPointValue();
		_shortStopPrice = StopLossPips > 0 ? entryPrice + StopLossPips * point : null;
		_shortTakeProfit = TakeProfitPips > 0 ? entryPrice - TakeProfitPips * point : null;
		_shortTrail = TrailingStopPips > 0 ? entryPrice + TrailingStopPips * point : null;
		_bestShortPrice = entryPrice;
		_longStopPrice = null;
		_longTakeProfit = null;
		_longTrail = null;
		_bestLongPrice = null;
	}

	private void ResetTradeState()
	{
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakeProfit = null;
		_shortTakeProfit = null;
		_longTrail = null;
		_shortTrail = null;
		_bestLongPrice = null;
		_bestShortPrice = null;
	}

	private decimal GetPointValue()
	{
		var point = Security?.PriceStep ?? 1m;
		return point > 0m ? point : 1m;
	}

	private static void ShiftSeries(decimal?[] series, decimal? value)
	{
		if (series.Length == 0)
		return;

		for (var i = series.Length - 1; i > 0; i--)
		{
			series[i] = series[i - 1];
		}

		series[0] = value;
	}

	private static bool HasSeriesValue(decimal?[] series, int shift, int depth)
	{
		var index = shift + depth;
		return index < series.Length && series[index].HasValue;
	}

	private static decimal? GetSeriesValue(decimal?[] series, int shift, int depth)
	{
		var index = shift + depth;
		return index < series.Length ? series[index] : null;
	}

	private static decimal? CombineLongStops(decimal? stopLoss, decimal? trailing)
	{
		if (stopLoss.HasValue && trailing.HasValue)
		return Math.Max(stopLoss.Value, trailing.Value);
		return stopLoss ?? trailing;
	}

	private static decimal? CombineShortStops(decimal? stopLoss, decimal? trailing)
	{
		if (stopLoss.HasValue && trailing.HasValue)
		return Math.Min(stopLoss.Value, trailing.Value);
		return stopLoss ?? trailing;
	}

	private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPriceTypes priceType)
	{
		return priceType switch
		{
			AppliedPriceTypes.Open => candle.OpenPrice,
			AppliedPriceTypes.High => candle.HighPrice,
			AppliedPriceTypes.Low => candle.LowPrice,
			AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + candle.ClosePrice + candle.ClosePrice) / 4m,
			_ => candle.ClosePrice
		};
	}

	private static IIndicator CreateMovingAverage(MaMethods method, int period)
	{
		return method switch
		{
			MaMethods.Simple => new SimpleMovingAverage { Length = period },
			MaMethods.Exponential => new ExponentialMovingAverage { Length = period },
			MaMethods.Smoothed => new SmoothedMovingAverage { Length = period },
			MaMethods.Weighted => new WeightedMovingAverage { Length = period },
			_ => new SimpleMovingAverage { Length = period }
		};
	}

	/// <summary>
	/// Moving average smoothing methods supported by the strategy.
	/// </summary>
	public enum MaMethods
	{
		Simple,
		Exponential,
		Smoothed,
		Weighted
	}

	/// <summary>
	/// Price sources supported for indicator calculations.
	/// </summary>
	public enum AppliedPriceTypes
	{
		Close,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted
	}
}