在 GitHub 上查看

Crossing of Two iMA v2 策略

概述

该策略将 MetaTrader 中的 “Crossing of two iMA v2” 专家顾问迁移到 StockSharp 的高级 API。策略利用两条带位移的移动平均线产生交叉信号,并可选用第三条移动平均线作为趋势过滤。通过止损/止盈、防御性追踪止损以及固定或按风险百分比计算的持仓量,尽可能重现原始 EA 的行为,同时满足 StockSharp 的开发规范。

指标与输入

  • 第一条移动平均线:可设置周期、位移、平滑方法以及取价方式。
  • 第二条移动平均线:拥有独立的一组配置参数。
  • 第三条移动平均线过滤器:可选的趋势过滤器;只有当第一条均线位于过滤均线下方时才允许做多,位于过滤均线上方时才允许做空。
  • K线类型:定义订阅数据的时间框架/数据源。

交易逻辑

步骤 1 – 即时交叉

  1. 每根收盘 K 线都会使用选定的价格类型更新所有移动平均指标。
  2. 当第一条均线在上一根与当前 K 线之间上穿第二条均线时触发多头信号。
  3. 当第一条均线在上一根与当前 K 线之间下穿第二条均线时触发空头信号。
  4. 启用过滤器时,多头信号要求第一条均线保持在过滤均线下方;空头信号要求第一条均线保持在过滤均线上方。

步骤 2 – 延迟确认

若步骤 1 未出现信号,策略会检查两根 K 线之前是否发生过交叉且仍然有效。这与原始 EA 会回溯近几根 K 线寻找遗漏信号的逻辑一致。为避免重复交易,只有在距离上一次成交至少 3 根 K 线后才允许触发该补充信号。

下单方式

  • 所有进场均采用市价单执行,若已有反向持仓会先平仓再建仓。
  • 当价格在当前 K 线触及止损、止盈或追踪止损价位时立即以市价单离场。

风险控制

  • 止损止盈以点(pip)为单位设定,并根据交易品种的 PriceStep(若缺省则使用 1)换算为价格差。
  • 追踪止损从入场价开始,随着价格向有利方向移动而上调。当最佳价格较上一止损位至少前进 TrailingStepPips 个点时,更新新的追踪止损。
  • 当固定止损与追踪止损同时存在时,策略会采用更保守的一侧(多头取较高价,空头取较低价)。

仓位管理

  • UseRiskPercenttrue 时,持仓量按 Equity * RiskPercent / (StopLossPips * PipValue) 计算。如果没有有效的止损设置,则回退到固定持仓量。
  • UseRiskPercentfalse 时,始终使用 FixedVolume
  • PipValue 应填写单手/单合约每个点的货币价值,以便正确换算风险。

实现细节

  • 策略仅基于收盘 K 线运行,并不会注册任何挂单。若需要止损/限价入场,可以在此基础上扩展实现。
  • 可关闭第三条移动平均线过滤器,从而在每次交叉时都交易(对应原始 EA 的 InpFilterMA = false)。
  • 请根据具体品种正确设置 K 线类型、价格最小变动及点值参数,以确保风险管理准确。

参数

名称 说明 默认值
FirstPeriod 第一条移动平均线的周期。 5
FirstShift 第一条移动平均线输出值的位移(以 K 线数计)。 3
FirstMethod 第一条移动平均线的平滑方法(SimpleExponentialSmoothedWeighted)。 Smoothed
FirstAppliedPrice 第一条移动平均线的取价方式(CloseOpenHighLowMedianTypicalWeighted)。 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 策略使用的 K 线数据类型/时间框架。 1 分钟 K 线
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
	}
}