Открыть на GitHub

Стратегия MA Crossover Multi Timeframe

Стратегия повторяет идею оригинального советника MA Crossover для MetaTrader 4. Сравниваются две скользящие средние, которые могут строиться по разным таймфреймам. Пересечение быстрый MA выше медленного открывает длинную позицию, пересечение вниз – короткую. Дополнительные фильтры управляют допустимым направлением торговли, торговым расписанием и защитой по просадке. Стоп-лосс, тейк-профит и трейлинг реализованы "скрыто", как и в версии MQL.

Логика работы

  1. Подписка на два потока свечей (текущий и предыдущий таймфреймы) и расчёт выбранного типа скользящих средних.
  2. Применение настроенных сдвигов по количеству завершённых баров перед сравнением значений.
  3. Игнорирование незавершённых свечей и ожидание формирования обоих индикаторов.
  4. Пропуск торговли вне заданного окна по дням и времени или при срабатывании защиты по equity.
  5. При бычьем пересечении:
    • Опционально закрыть короткую позицию, если ClosePositionsOnCross = true.
    • Открыть длинную позицию, если разрешена торговля в лонг.
  6. При медвежьем пересечении:
    • Опционально закрыть длинную позицию, если ClosePositionsOnCross = true.
    • Открыть короткую позицию, если разрешена торговля в шорт.
  7. Управление открытой позицией с помощью стоп-лосса, тейк-профита и трейлинга, заданных в процентах от цены входа.

Параметры

Параметр Описание
AllowedDirection Ограничение направления торговли (LongOnly, ShortOnly, LongAndShort).
ClosePositionsOnCross Закрывать противоположную позицию при новом пересечении.
MaType Тип вычисления скользящей средней (Simple, Exponential, Smoothed, Weighted).
CurrentMaPeriod Период быстрой скользящей.
PreviousPeriodAddition Добавка к периоду медленной скользящей (PreviousMaPeriod = CurrentMaPeriod + addition).
CurrentShift / PreviousShift Сдвиг значений скользящих по числу завершённых баров.
CurrentCandleType / PreviousCandleType Свечные данные для расчёта быстрой и медленной скользящих.
StopLossPercent Размер стоп-лосса в процентах от цены входа (скрытое закрытие).
TrailingStopPercent Процент трейлинга, рассчитываемый от лучшей достигнутой цены.
TakeProfitPercent Размер тейк-профита в процентах от цены входа (скрытое закрытие).
StartDay / EndDay Фильтр по дням недели.
StartTime / EndTime Временное окно внутри дня для открытия новых сделок.
ClosePositionsOnMinEquity Закрывать позиции при срабатывании защиты по equity.
MinimumEquityPercent Минимально допустимый процент от стартовой стоимости портфеля.

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

  • Стоп-лосс, тейк-профит и трейлинг рассчитываются внутри стратегии и исполняются рыночными заявками, что повторяет скрытую защиту из MQL-версии.
  • MinimumEquityPercent фиксирует первоначальную стоимость портфеля и может вынудить закрыть все позиции при падении equity ниже порога.
  • Размер позиции задаётся свойством Strategy.Volume. По умолчанию объём равен 1.

Практические рекомендации

  • Необходимо обеспечить поставку свечей для обоих выбранных таймфреймов.
  • Даже при совпадении таймфреймов стратегия создаёт две подписки для симметричной обработки сигналов.
  • Защитные заявки не выставляются в стакан, так как выходы исполняются рыночными приказами.
  • Параметры соответствуют ключевым настройкам советника в MQL; функции усреднения и хеджирования не перенесены из-за особенностей API StockSharp.

Отличия от исходного MQL-кода

  • Не реализованы опции усреднения (Average_Up, Average_Down) и хеджирование.
  • Защита по equity основана на значении портфеля в StockSharp, а не на свободной марже брокера.
  • Выходы по риску выполняются на закрытии свечи рыночными приказами и не видны в заявках.
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;

public class MaCrossoverMultiTimeframeStrategy : Strategy
{
	private readonly StrategyParam<TradeDirectionOptions> _allowedDirection;
	private readonly StrategyParam<bool> _closeOnCross;
	private readonly StrategyParam<MovingAverageTypeOptions> _maType;
	private readonly StrategyParam<int> _currentPeriod;
	private readonly StrategyParam<int> _previousPeriodAdd;
	private readonly StrategyParam<int> _currentShift;
	private readonly StrategyParam<int> _previousShift;
	private readonly StrategyParam<DataType> _currentCandleType;
	private readonly StrategyParam<DataType> _previousCandleType;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<decimal> _trailingStopPercent;
	private readonly StrategyParam<decimal> _takeProfitPercent;
	private readonly StrategyParam<DayOfWeek> _startDay;
	private readonly StrategyParam<DayOfWeek> _endDay;
	private readonly StrategyParam<TimeSpan> _startTime;
	private readonly StrategyParam<TimeSpan> _endTime;
	private readonly StrategyParam<bool> _closeOnMinEquity;
	private readonly StrategyParam<decimal> _minimumEquityPercent;

	private IIndicator _currentMaIndicator;
	private IIndicator _previousMaIndicator;

	private readonly Queue<decimal> _currentShiftBuffer = new();
	private readonly Queue<decimal> _previousShiftBuffer = new();

	private decimal? _currentMaValue;
	private decimal? _previousMaValue;
	private bool? _wasCurrentAbovePrevious;

	private decimal _entryPrice;
	private decimal _highestPrice;
	private decimal _lowestPrice;
	private decimal _previousPosition;
	private decimal? _initialPortfolioValue;

	/// <summary>
	/// Allowed trade direction.
	/// </summary>
	public TradeDirectionOptions AllowedDirection
	{
		get => _allowedDirection.Value;
		set => _allowedDirection.Value = value;
	}

	/// <summary>
	/// Close opposite positions when a crossover happens.
	/// </summary>
	public bool ClosePositionsOnCross
	{
		get => _closeOnCross.Value;
		set => _closeOnCross.Value = value;
	}

	/// <summary>
	/// Moving average calculation type.
	/// </summary>
	public MovingAverageTypeOptions MaType
	{
		get => _maType.Value;
		set => _maType.Value = value;
	}

	/// <summary>
	/// Period for the current timeframe moving average.
	/// </summary>
	public int CurrentMaPeriod
	{
		get => _currentPeriod.Value;
		set => _currentPeriod.Value = value;
	}

	/// <summary>
	/// Additional length added to the previous moving average.
	/// </summary>
	public int PreviousPeriodAddition
	{
		get => _previousPeriodAdd.Value;
		set => _previousPeriodAdd.Value = value;
	}

	/// <summary>
	/// Shift applied to the current moving average.
	/// </summary>
	public int CurrentShift
	{
		get => _currentShift.Value;
		set => _currentShift.Value = value;
	}

	/// <summary>
	/// Shift applied to the previous moving average.
	/// </summary>
	public int PreviousShift
	{
		get => _previousShift.Value;
		set => _previousShift.Value = value;
	}

	/// <summary>
	/// Candle type for the current moving average.
	/// </summary>
	public DataType CurrentCandleType
	{
		get => _currentCandleType.Value;
		set => _currentCandleType.Value = value;
	}

	/// <summary>
	/// Candle type for the previous moving average.
	/// </summary>
	public DataType PreviousCandleType
	{
		get => _previousCandleType.Value;
		set => _previousCandleType.Value = value;
	}

	/// <summary>
	/// Stop-loss percentage relative to the entry price.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

	/// <summary>
	/// Trailing stop percentage.
	/// </summary>
	public decimal TrailingStopPercent
	{
		get => _trailingStopPercent.Value;
		set => _trailingStopPercent.Value = value;
	}

	/// <summary>
	/// Take-profit percentage relative to the entry price.
	/// </summary>
	public decimal TakeProfitPercent
	{
		get => _takeProfitPercent.Value;
		set => _takeProfitPercent.Value = value;
	}

	/// <summary>
	/// First trading day of the schedule.
	/// </summary>
	public DayOfWeek StartDay
	{
		get => _startDay.Value;
		set => _startDay.Value = value;
	}

	/// <summary>
	/// Last trading day of the schedule.
	/// </summary>
	public DayOfWeek EndDay
	{
		get => _endDay.Value;
		set => _endDay.Value = value;
	}

	/// <summary>
	/// Start time of the trading window.
	/// </summary>
	public TimeSpan StartTime
	{
		get => _startTime.Value;
		set => _startTime.Value = value;
	}

	/// <summary>
	/// End time of the trading window.
	/// </summary>
	public TimeSpan EndTime
	{
		get => _endTime.Value;
		set => _endTime.Value = value;
	}

	/// <summary>
	/// Close all positions when the equity guard is triggered.
	/// </summary>
	public bool ClosePositionsOnMinEquity
	{
		get => _closeOnMinEquity.Value;
		set => _closeOnMinEquity.Value = value;
	}

	/// <summary>
	/// Minimum equity percentage relative to the initial portfolio value.
	/// </summary>
	public decimal MinimumEquityPercent
	{
		get => _minimumEquityPercent.Value;
		set => _minimumEquityPercent.Value = value;
	}

	/// <summary>
	/// Period calculated for the previous moving average.
	/// </summary>
	public int PreviousMaPeriod => Math.Max(1, CurrentMaPeriod + PreviousPeriodAddition);

	/// <summary>
	/// Initializes the strategy parameters.
	/// </summary>
	public MaCrossoverMultiTimeframeStrategy()
	{
		Volume = 1;

		_allowedDirection = Param(nameof(AllowedDirection), TradeDirectionOptions.LongAndShort)
			.SetDisplay("Trade Direction", "Allowed direction for opening positions", "Trading");

		_closeOnCross = Param(nameof(ClosePositionsOnCross), true)
			.SetDisplay("Close on Cross", "Close existing opposite positions when moving averages cross", "Trading");

		_maType = Param(nameof(MaType), MovingAverageTypeOptions.Exponential)
			.SetDisplay("MA Type", "Moving average calculation method", "Indicators");

		_currentPeriod = Param(nameof(CurrentMaPeriod), 42)
			.SetGreaterThanZero()
			.SetDisplay("Current MA Period", "Length of the faster moving average", "Indicators")
			
			.SetOptimize(10, 120, 5);

		_previousPeriodAdd = Param(nameof(PreviousPeriodAddition), 10)
			.SetNotNegative()
			.SetDisplay("Previous MA Extra Length", "Additional length added to the slower moving average", "Indicators")
			
			.SetOptimize(0, 50, 5);

		_currentShift = Param(nameof(CurrentShift), 0)
			.SetNotNegative()
			.SetDisplay("Current MA Shift", "Number of bars to shift the faster moving average", "Indicators");

		_previousShift = Param(nameof(PreviousShift), 2)
			.SetNotNegative()
			.SetDisplay("Previous MA Shift", "Number of bars to shift the slower moving average", "Indicators");

		_currentCandleType = Param(nameof(CurrentCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Current Candle", "Timeframe used for the faster moving average", "Data");

		_previousCandleType = Param(nameof(PreviousCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Previous Candle", "Timeframe used for the slower moving average", "Data");

		_stopLossPercent = Param(nameof(StopLossPercent), 0m)
			.SetNotNegative()
			.SetDisplay("Stop Loss %", "Stop-loss percentage from the entry price", "Risk")
			
			.SetOptimize(0m, 10m, 1m);

		_trailingStopPercent = Param(nameof(TrailingStopPercent), 0m)
			.SetNotNegative()
			.SetDisplay("Trailing Stop %", "Trailing stop percentage applied to the best price", "Risk")
			
			.SetOptimize(0m, 10m, 1m);

		_takeProfitPercent = Param(nameof(TakeProfitPercent), 0m)
			.SetNotNegative()
			.SetDisplay("Take Profit %", "Take-profit percentage from the entry price", "Risk")
			
			.SetOptimize(0m, 20m, 1m);

		_startDay = Param(nameof(StartDay), DayOfWeek.Monday)
			.SetDisplay("Start Day", "First day when trading is allowed", "Schedule");

		_endDay = Param(nameof(EndDay), DayOfWeek.Friday)
			.SetDisplay("End Day", "Last day when trading is allowed", "Schedule");

		_startTime = Param(nameof(StartTime), TimeSpan.Zero)
			.SetDisplay("Start Time", "Daily time when the strategy begins trading", "Schedule");

		_endTime = Param(nameof(EndTime), new TimeSpan(23, 59, 0))
			.SetDisplay("End Time", "Daily time when the strategy stops opening new trades", "Schedule");

		_closeOnMinEquity = Param(nameof(ClosePositionsOnMinEquity), true)
			.SetDisplay("Close on Equity Guard", "Close positions when equity drops below the threshold", "Risk");

		_minimumEquityPercent = Param(nameof(MinimumEquityPercent), 0m)
			.SetNotNegative()
			.SetDisplay("Minimum Equity %", "Minimum equity percentage relative to the initial value", "Risk")
			
			.SetOptimize(0m, 100m, 5m);
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		if (Security == null)
			yield break;

		yield return (Security, CurrentCandleType);
		if (!Equals(PreviousCandleType, CurrentCandleType))
			yield return (Security, PreviousCandleType);
	}

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

		_currentMaIndicator?.Reset();
		_previousMaIndicator?.Reset();

		_currentShiftBuffer.Clear();
		_previousShiftBuffer.Clear();

		_currentMaValue = null;
		_previousMaValue = null;
		_wasCurrentAbovePrevious = null;

		ResetPositionState();
		_previousPosition = 0m;
		_initialPortfolioValue = null;
	}

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

		_initialPortfolioValue = Portfolio?.CurrentValue;
		// Remember the starting equity for the guard logic.

		_currentMaIndicator = CreateMovingAverage(MaType, CurrentMaPeriod);
		_previousMaIndicator = CreateMovingAverage(MaType, PreviousMaPeriod);

		var currentSubscription = SubscribeCandles(CurrentCandleType);
		// Bind the fast moving average to the current timeframe.
		currentSubscription.Bind(_currentMaIndicator, OnCurrentCandle).Start();

		var previousSubscription = SubscribeCandles(PreviousCandleType);
		// Bind the slow moving average to the configured timeframe.
		previousSubscription.Bind(_previousMaIndicator, OnPreviousCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, currentSubscription);
			DrawIndicator(area, _currentMaIndicator);
			DrawIndicator(area, _previousMaIndicator);
			DrawOwnTrades(area);
		}
	}

	private void OnCurrentCandle(ICandleMessage candle, decimal maValue)
	{
		// Process only completed candles to avoid premature reactions.
		if (candle.State != CandleStates.Finished)
			return;

		if (!_currentMaIndicator.IsFormed)
			return;

		var shifted = ApplyShift(CurrentShift, _currentShiftBuffer, maValue);
		if (shifted == null)
			return;

		_currentMaValue = shifted;

		if (!CheckFreeEquityGuard())
			return;

		ManagePosition(candle);
		TryProcessSignal(candle);
	}

	private void OnPreviousCandle(ICandleMessage candle, decimal maValue)
	{
		// Update the reference moving average from the second timeframe.
		if (candle.State != CandleStates.Finished)
			return;

		if (!_previousMaIndicator.IsFormed)
			return;

		var shifted = ApplyShift(PreviousShift, _previousShiftBuffer, maValue);
		if (shifted == null)
			return;

		_previousMaValue = shifted;
	}

	private void TryProcessSignal(ICandleMessage candle)
	{
		// Ensure that both moving averages are available and trading is allowed.
		if (_currentMaValue == null || _previousMaValue == null)
			return;


		if (!IsWithinTradingWindow(candle.OpenTime))
			return;

		var isCurrentAbove = _currentMaValue.Value > _previousMaValue.Value;

		if (_wasCurrentAbovePrevious == null)
		{
			_wasCurrentAbovePrevious = isCurrentAbove;
			return;
		}

		if (_wasCurrentAbovePrevious == isCurrentAbove)
			return;

		if (isCurrentAbove)
		{
			HandleBullishCross(candle);
		}
		else
		{
			HandleBearishCross(candle);
		}

		_wasCurrentAbovePrevious = isCurrentAbove;
	}

	private void HandleBullishCross(ICandleMessage candle)
	{
		// Prevent duplicate entries and respect direction filters.
		if (!IsLongAllowed())
			return;

		if (Position > 0)
			return;

		var volume = Volume;
		if (volume <= 0m)
			volume = 1m;

		if (Position < 0)
		{
			if (!ClosePositionsOnCross)
				return;

			volume += Math.Abs(Position);
		}

		BuyMarket(volume);

	}

	private void HandleBearishCross(ICandleMessage candle)
	{
		// Prevent duplicate entries and respect direction filters.
		if (!IsShortAllowed())
			return;

		if (Position < 0)
			return;

		var volume = Volume;
		if (volume <= 0m)
			volume = 1m;

		if (Position > 0)
		{
			if (!ClosePositionsOnCross)
				return;

			volume += Math.Abs(Position);
		}

		SellMarket(volume);

	}

	private void ManagePosition(ICandleMessage candle)
	{
		// Translate percentage-based risk settings into market exits.
		if (Position == 0 || _entryPrice <= 0m)
			return;

		var stopLoss = StopLossPercent / 100m;
		var takeProfit = TakeProfitPercent / 100m;
		var trailing = TrailingStopPercent / 100m;
		var closePrice = candle.ClosePrice;

		if (Position > 0)
		{
			if (closePrice > _highestPrice)
				_highestPrice = closePrice;

			if (stopLoss > 0m)
			{
				var stopPrice = _entryPrice * (1m - stopLoss);
				if (closePrice <= stopPrice)
				{
					SellMarket(Math.Abs(Position));
	
					return;
				}
			}

			if (takeProfit > 0m)
			{
				var targetPrice = _entryPrice * (1m + takeProfit);
				if (closePrice >= targetPrice)
				{
					SellMarket(Math.Abs(Position));
	
					return;
				}
			}

			if (trailing > 0m && _highestPrice > 0m)
			{
				var trailingPrice = _highestPrice * (1m - trailing);
				if (closePrice <= trailingPrice)
				{
					SellMarket(Math.Abs(Position));
	
					return;
				}
			}
		}
		else if (Position < 0)
		{
			if (_lowestPrice == 0m || closePrice < _lowestPrice)
				_lowestPrice = closePrice;

			if (stopLoss > 0m)
			{
				var stopPrice = _entryPrice * (1m + stopLoss);
				if (closePrice >= stopPrice)
				{
					BuyMarket(Math.Abs(Position));
	
					return;
				}
			}

			if (takeProfit > 0m)
			{
				var targetPrice = _entryPrice * (1m - takeProfit);
				if (closePrice <= targetPrice)
				{
					BuyMarket(Math.Abs(Position));
	
					return;
				}
			}

			if (trailing > 0m && _lowestPrice > 0m)
			{
				var trailingPrice = _lowestPrice * (1m + trailing);
				if (closePrice >= trailingPrice)
				{
					BuyMarket(Math.Abs(Position));
	
					return;
				}
			}
		}
	}

	private bool CheckFreeEquityGuard()
	{
		// Abort new trades if the equity guard has been triggered.
		var threshold = MinimumEquityPercent;
		if (threshold <= 0m)
			return true;

		if (_initialPortfolioValue == null || _initialPortfolioValue <= 0m)
			return true;

		var currentValue = Portfolio?.CurrentValue;
		if (currentValue == null)
			return true;

		var minimumEquity = _initialPortfolioValue.Value * (threshold / 100m);
		if (currentValue.Value > minimumEquity)
			return true;



		if (ClosePositionsOnMinEquity && Position != 0)
		{
			CloseAllPositions();
		}

		return false;
	}

	private void CloseAllPositions()
	{
		// Exit using market orders because the protection stays hidden.
		if (Position > 0)
			SellMarket(Math.Abs(Position));
		else if (Position < 0)
			BuyMarket(Math.Abs(Position));
	}

	private bool IsWithinTradingWindow(DateTimeOffset time)
	{
		var day = time.DayOfWeek;
		var startDay = StartDay;
		var endDay = EndDay;

		var withinDays = startDay <= endDay
			? day >= startDay && day <= endDay
			: day >= startDay || day <= endDay;

		if (!withinDays)
			return false;

		var startTime = StartTime;
		var endTime = EndTime;
		var timeOfDay = time.TimeOfDay;

		return startTime <= endTime
			? timeOfDay >= startTime && timeOfDay <= endTime
			: timeOfDay >= startTime || timeOfDay <= endTime;
	}

	private static decimal? ApplyShift(int shift, Queue<decimal> buffer, decimal value)
	{
		// Maintain a small buffer to emulate the MQL shift parameter.
		if (shift <= 0)
		{
			buffer.Clear();
			return value;
		}

		buffer.Enqueue(value);

		while (buffer.Count > shift + 1)
			buffer.Dequeue();

		return buffer.Count == shift + 1 ? buffer.Peek() : null;
	}

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

	private bool IsLongAllowed() => AllowedDirection != TradeDirectionOptions.ShortOnly;

	private bool IsShortAllowed() => AllowedDirection != TradeDirectionOptions.LongOnly;

	private void ResetPositionState()
	{
		_entryPrice = 0m;
		_highestPrice = 0m;
		_lowestPrice = 0m;
		_previousPosition = 0m;
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		// Update the average entry price once fills arrive.
		base.OnOwnTradeReceived(trade);

		if (trade.Order.Security != Security)
			return;

		var currentPosition = Position;

		if (_previousPosition <= 0m && currentPosition > 0m)
		{
			_entryPrice = trade.Trade.Price;
			_highestPrice = trade.Trade.Price;
			_lowestPrice = trade.Trade.Price;
		}
		else if (_previousPosition >= 0m && currentPosition < 0m)
		{
			_entryPrice = trade.Trade.Price;
			_highestPrice = trade.Trade.Price;
			_lowestPrice = trade.Trade.Price;
		}

		if (currentPosition > 0m && trade.Order.Side == Sides.Buy)
		{
			var totalVolume = Math.Abs(currentPosition);
			var previousVolume = Math.Abs(_previousPosition > 0m ? _previousPosition : 0m);
			var tradeVolume = trade.Trade.Volume;
			if (totalVolume > 0m)
			{
				var weighted = (_entryPrice * previousVolume) + (trade.Trade.Price * tradeVolume);
				_entryPrice = weighted / totalVolume;
			}

			if (trade.Trade.Price > _highestPrice)
				_highestPrice = trade.Trade.Price;
		}
		else if (currentPosition < 0m && trade.Order.Side == Sides.Sell)
		{
			var totalVolume = Math.Abs(currentPosition);
			var previousVolume = Math.Abs(_previousPosition < 0m ? _previousPosition : 0m);
			var tradeVolume = trade.Trade.Volume;
			if (totalVolume > 0m)
			{
				var weighted = (_entryPrice * previousVolume) + (trade.Trade.Price * tradeVolume);
				_entryPrice = weighted / totalVolume;
			}

			if (_lowestPrice == 0m || trade.Trade.Price < _lowestPrice)
				_lowestPrice = trade.Trade.Price;
		}

		_previousPosition = currentPosition;
	}

	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);

		if (Position == 0m)
			ResetPositionState();
	}

	public enum TradeDirectionOptions
	{
		LongOnly,
		ShortOnly,
		LongAndShort
	}

	public enum MovingAverageTypeOptions
	{
		Simple,
		Exponential,
		Smoothed,
		Weighted
	}
}