Ver no GitHub

Estratégia de Cruzamento de MA em Múltiplos Períodos

Esta estratégia reproduz a ideia do expert advisor original MA Crossover para MetaTrader 4. Ela compara duas médias móveis que podem vir de diferentes períodos. Um cruzamento altista (MA rápida acima da MA lenta) abre uma posição comprada, enquanto um cruzamento baixista abre uma posição vendida. Filtros opcionais controlam a direção de operação permitida, o horário de trading ativo e um guardião de equity. A lógica interna de stop-loss, take-profit e trailing emula as saídas "ocultas" da versão MQL.

Lógica de trading

  1. Inscrever-se em dois fluxos de velas (períodos atual e anterior) e calcular o tipo selecionado de médias móveis.
  2. Aplicar os deslocamentos de barra configurados nos valores da média móvel antes de compará-los.
  3. Ignorar velas não concluídas e aguardar ambas as médias móveis estarem formadas.
  4. Pular o trading fora da janela de dia/hora configurada ou quando o guardião de equity for acionado.
  5. Em um cruzamento altista:
    • Opcionalmente fechar uma posição vendida se ClosePositionsOnCross = true.
    • Abrir uma posição comprada se o trading comprado for permitido.
  6. Em um cruzamento baixista:
    • Opcionalmente fechar uma posição comprada se ClosePositionsOnCross = true.
    • Abrir uma posição vendida se o trading vendido for permitido.
  7. Gerenciar a posição aberta com regras de stop-loss, take-profit e trailing expressas como percentuais do preço de entrada.

Parâmetros

Parâmetro Descrição
AllowedDirection Filtro de direção de operação (LongOnly, ShortOnly, LongAndShort).
ClosePositionsOnCross Fechar a posição oposta quando um cruzamento aparecer antes de abrir uma nova operação.
MaType Tipo de cálculo de média móvel (Simple, Exponential, Smoothed, Weighted).
CurrentMaPeriod Período para a média móvel rápida.
PreviousPeriodAddition Comprimento extra adicionado à média móvel lenta (PreviousMaPeriod = CurrentMaPeriod + addition).
CurrentShift / PreviousShift Número de barras concluídas usadas para deslocar os valores da média móvel para trás.
CurrentCandleType / PreviousCandleType Dados de vela para as médias móveis rápidas e lentas.
StopLossPercent Distância de stop-loss em percentual do preço de entrada (saída oculta).
TrailingStopPercent Distância de trailing stop em percentual baseado no melhor preço alcançado.
TakeProfitPercent Distância de take-profit em percentual do preço de entrada (saída oculta).
StartDay / EndDay Filtro de dia da semana para atividade de trading.
StartTime / EndTime Janela de tempo intradiário para abertura de novas operações.
ClosePositionsOnMinEquity Fechar todas as posições quando o guardião de equity for acionado.
MinimumEquityPercent Percentual mínimo do valor inicial do portfólio permitido pelo guardião de equity.

Gestão de risco

  • A estratégia calcula os níveis de stop-loss, take-profit e trailing internamente e sai via ordens de mercado, imitando a lógica de proteção oculta do script MQL.
  • MinimumEquityPercent armazena o valor inicial do portfólio na inicialização e pode acionar um nivelamento forçado se o equity cair abaixo do limite.
  • O tamanho da posição é controlado através da propriedade base Strategy.Volume. O volume padrão é definido como 1.

Notas de uso

  • A estratégia requer dados de velas para ambos os períodos configurados. Certifique-se de que os conectores associados suportem os períodos solicitados.
  • Quando ambas as médias móveis usam o mesmo período, a estratégia ainda se inscreve em dois fluxos para manter a lógica simétrica.
  • Como as saídas por stop e take-profit são executadas via ordens de mercado, nenhuma ordem de proteção permanece no livro de ordens.
  • Os parâmetros correspondem às entradas principais do expert advisor MQL original, enquanto os recursos de gestão de risco/margem que dependem de funções específicas do broker (hedge, averaging) são intencionalmente omitidos.

Diferenças da versão MQL

  • Recursos de averaging (Average_Up, Average_Down) e configurações de hedge não estão implementados para manter a lógica compatível com a API de alto nível do StockSharp.
  • O guardião de equity usa o valor do portfólio do StockSharp em vez de cálculos específicos de margem livre.
  • As saídas por risco são executadas através de ordens de mercado em eventos de fechamento de vela e são, portanto, sempre ocultas do livro de ordens.
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
	}
}