Ver en GitHub

Estrategia EA Moving Average

Descripción general

  • Convertida del asesor experto de MetaTrader "EA Moving Average" (edición barabashkakvn).
  • Utiliza cuatro medias móviles independientes para controlar las entradas y salidas largas y cortas.
  • Diseñada para un único símbolo en modo netting. El tipo de vela predeterminado es el marco temporal de 15 minutos, pero se puede seleccionar cualquier tipo de vela regular.
  • La estrategia abre como máximo una posición a la vez. Mientras una posición está activa, solo se evalúan las reglas de salida.

Lógica de trading

Entrada larga

  1. La vela actual debe cerrar por encima de la media móvil Buy Open después de abrir por debajo de ella (cruce verdadero dentro de una sola barra).
  2. UseBuy debe estar habilitado.
  3. Si ConsiderPriceLastOut está habilitado, el precio actual debe ser menor o igual al precio de la última operación cerrada. Esto evita comprar por encima de la salida más reciente.
  4. Cuando se cumplen las condiciones, la estrategia envía una orden de compra a mercado dimensionada por el modelo de riesgo.

Salida larga

  1. Activa solo mientras la posición neta es larga.
  2. La vela debe abrir por encima de la media móvil Buy Close y cerrar de vuelta por debajo de ella, señalando un cruce bajista.
  3. Cuando se activa, toda la posición se cierra con una orden de mercado.

Entrada corta

  1. La vela debe cerrar por debajo de la media móvil Sell Open después de abrir por encima de ella.
  2. UseSell debe estar habilitado.
  3. Si ConsiderPriceLastOut está habilitado, el precio actual debe ser mayor o igual al último precio de salida. Esto evita ponerse corto por debajo de la cobertura previa.
  4. Se envía una orden de venta a mercado utilizando el volumen basado en el riesgo.

Salida corta

  1. Activa solo mientras la posición es corta.
  2. La vela debe abrir por debajo de la media móvil Sell Close y cerrar por encima de ella.
  3. La posición corta se cubre completamente a mercado.

Riesgo y dimensionamiento de posición

  • MaximumRisk expresa el capital de riesgo por operación como una fracción del capital del portafolio. La estrategia divide este monto de riesgo por el precio actual para obtener una estimación de volumen bruto.
  • DecreaseFactor emula la reducción de lote original de MetaTrader. Después de dos o más operaciones perdedoras consecutivas, el volumen se reduce proporcionalmente a la racha de pérdidas dividida por DecreaseFactor.
  • Los volúmenes se alinean al paso de volumen del instrumento y nunca caen por debajo de un paso. Si el cálculo de riesgo falla, el valor de respaldo es la propiedad Volume de la estrategia (por defecto 1 contrato/lote).

Parámetros

Parámetro Predeterminado Descripción
MaximumRisk 0.02 Fracción del capital arriesgado por operación.
DecreaseFactor 3 Factor de reducción de lote después de pérdidas consecutivas. Use 0 para deshabilitar.
BuyOpenPeriod 30 Período de la media móvil usada para entradas largas.
BuyOpenShift 3 Desplazamiento hacia adelante (barras) aplicado a la media móvil de entrada larga.
BuyOpenMethod Exponential Método de media móvil para entradas largas (Simple, Exponential, Smoothed, LinearWeighted).
BuyOpenPrice Close Entrada de precio para la media móvil de entrada larga.
BuyClosePeriod 14 Período de la media móvil de salida larga.
BuyCloseShift 3 Desplazamiento (barras) aplicado a la media móvil de salida larga.
BuyCloseMethod Exponential Método de la media móvil de salida larga.
BuyClosePrice Close Entrada de precio para la media móvil de salida larga.
SellOpenPeriod 30 Período de la media móvil de entrada corta.
SellOpenShift 0 Desplazamiento (barras) aplicado a la media móvil de entrada corta.
SellOpenMethod Exponential Método de la media móvil de entrada corta.
SellOpenPrice Close Entrada de precio para la media móvil de entrada corta.
SellClosePeriod 20 Período de la media móvil de salida corta.
SellCloseShift 2 Desplazamiento (barras) aplicado a la media móvil de salida corta.
SellCloseMethod Exponential Método de la media móvil de salida corta.
SellClosePrice Close Entrada de precio para la media móvil de salida corta.
UseBuy true Habilitar o deshabilitar operaciones largas.
UseSell true Habilitar o deshabilitar operaciones cortas.
ConsiderPriceLastOut true Requerir mejora de precio respecto a la última salida antes de re-entrar.
CandleType Marco temporal 15m Serie de velas usada para cálculos.

Notas adicionales

  • El último precio de salida y el contador de pérdidas consecutivas se rastrean desde las ejecuciones de operaciones, reflejando el comportamiento de MetaTrader.
  • Debido a que StockSharp ejecuta en velas terminadas, el filtro de precio de entrada compara con el precio de cierre de la vela, lo que aproxima la comparación original de ask/bid basada en ticks.
  • La estrategia asume una cuenta de netting; no se soporta cobertura de múltiples posiciones simultáneamente.
  • Siempre valide la configuración con pruebas históricas antes de operar con capital real.
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;
using Ecng.Logging;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Conversion of the "EA Moving Average" MetaTrader strategy.
/// Uses four configurable moving averages to define entry and exit rules for long and short trades.
/// Risk per trade is managed through a fixed percentage of account equity with optional lot reduction after consecutive losses.
/// </summary>
public class EaMovingAverageStrategy : Strategy
{
	/// <summary>
	/// Moving average calculation methods supported by the strategy.
	/// </summary>
	public enum MaMethods
	{
		Simple,
		Exponential,
		Smoothed,
		LinearWeighted
	}

	/// <summary>
	/// Price inputs supported by the moving average calculations.
	/// </summary>
	public enum MaPriceTypes
	{
		Close,
		Open,
		High,
		Low,
		Median,
		Typical,
		Weighted
	}
	private readonly StrategyParam<decimal> _maximumRisk;
	private readonly StrategyParam<decimal> _decreaseFactor;

	private readonly StrategyParam<int> _buyOpenPeriod;
	private readonly StrategyParam<int> _buyOpenShift;
	private readonly StrategyParam<MaMethods> _buyOpenMethod;
	private readonly StrategyParam<MaPriceTypes> _buyOpenPrice;

	private readonly StrategyParam<int> _buyClosePeriod;
	private readonly StrategyParam<int> _buyCloseShift;
	private readonly StrategyParam<MaMethods> _buyCloseMethod;
	private readonly StrategyParam<MaPriceTypes> _buyClosePrice;

	private readonly StrategyParam<int> _sellOpenPeriod;
	private readonly StrategyParam<int> _sellOpenShift;
	private readonly StrategyParam<MaMethods> _sellOpenMethod;
	private readonly StrategyParam<MaPriceTypes> _sellOpenPrice;

	private readonly StrategyParam<int> _sellClosePeriod;
	private readonly StrategyParam<int> _sellCloseShift;
	private readonly StrategyParam<MaMethods> _sellCloseMethod;
	private readonly StrategyParam<MaPriceTypes> _sellClosePrice;

	private readonly StrategyParam<bool> _useBuy;
	private readonly StrategyParam<bool> _useSell;
	private readonly StrategyParam<bool> _considerPriceLastOut;
	private readonly StrategyParam<DataType> _candleType;

	private DecimalLengthIndicator _buyOpenMa;
	private DecimalLengthIndicator _buyCloseMa;
	private DecimalLengthIndicator _sellOpenMa;
	private DecimalLengthIndicator _sellCloseMa;

	private readonly Queue<decimal> _buyOpenBuffer = new();
	private readonly Queue<decimal> _buyCloseBuffer = new();
	private readonly Queue<decimal> _sellOpenBuffer = new();
	private readonly Queue<decimal> _sellCloseBuffer = new();

	private decimal _lastExitPrice;
	private decimal _lastEntryPrice;
	private Sides? _lastEntrySide;
	private decimal _signedPosition;
	private int _consecutiveLosses;

	/// <summary>
	/// Initializes a new instance of the <see cref="EaMovingAverageStrategy"/> class.
	/// </summary>
	public EaMovingAverageStrategy()
	{
		_maximumRisk = Param(nameof(MaximumRisk), 0.02m)
			.SetNotNegative()
			.SetDisplay("Maximum Risk", "Risk per trade as part of equity", "Risk");

		_decreaseFactor = Param(nameof(DecreaseFactor), 3m)
			.SetNotNegative()
			.SetDisplay("Decrease Factor", "Lot reduction factor after losses", "Risk");

		_buyOpenPeriod = Param(nameof(BuyOpenPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Buy Open MA Period", "Moving average period for buy entries", "Buy Entry")
			
			.SetOptimize(5, 80, 5);

		_buyOpenShift = Param(nameof(BuyOpenShift), 3)
			.SetNotNegative()
			.SetDisplay("Buy Open MA Shift", "Shift in bars for the buy entry MA", "Buy Entry");

		_buyOpenMethod = Param(nameof(BuyOpenMethod), MaMethods.Exponential)
			.SetDisplay("Buy Open MA Method", "Moving average method for buy entries", "Buy Entry");

		_buyOpenPrice = Param(nameof(BuyOpenPrice), MaPriceTypes.Close)
			.SetDisplay("Buy Open Price", "Price type supplied to the buy entry MA", "Buy Entry");

		_buyClosePeriod = Param(nameof(BuyClosePeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Buy Close MA Period", "Moving average period for buy exits", "Buy Exit")
			
			.SetOptimize(5, 60, 5);

		_buyCloseShift = Param(nameof(BuyCloseShift), 3)
			.SetNotNegative()
			.SetDisplay("Buy Close MA Shift", "Shift in bars for the buy exit MA", "Buy Exit");

		_buyCloseMethod = Param(nameof(BuyCloseMethod), MaMethods.Exponential)
			.SetDisplay("Buy Close MA Method", "Moving average method for buy exits", "Buy Exit");

		_buyClosePrice = Param(nameof(BuyClosePrice), MaPriceTypes.Close)
			.SetDisplay("Buy Close Price", "Price type supplied to the buy exit MA", "Buy Exit");

		_sellOpenPeriod = Param(nameof(SellOpenPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Sell Open MA Period", "Moving average period for sell entries", "Sell Entry")
			
			.SetOptimize(5, 80, 5);

		_sellOpenShift = Param(nameof(SellOpenShift), 0)
			.SetNotNegative()
			.SetDisplay("Sell Open MA Shift", "Shift in bars for the sell entry MA", "Sell Entry");

		_sellOpenMethod = Param(nameof(SellOpenMethod), MaMethods.Exponential)
			.SetDisplay("Sell Open MA Method", "Moving average method for sell entries", "Sell Entry");

		_sellOpenPrice = Param(nameof(SellOpenPrice), MaPriceTypes.Close)
			.SetDisplay("Sell Open Price", "Price type supplied to the sell entry MA", "Sell Entry");

		_sellClosePeriod = Param(nameof(SellClosePeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Sell Close MA Period", "Moving average period for sell exits", "Sell Exit")
			
			.SetOptimize(5, 80, 5);

		_sellCloseShift = Param(nameof(SellCloseShift), 2)
			.SetNotNegative()
			.SetDisplay("Sell Close MA Shift", "Shift in bars for the sell exit MA", "Sell Exit");

		_sellCloseMethod = Param(nameof(SellCloseMethod), MaMethods.Exponential)
			.SetDisplay("Sell Close MA Method", "Moving average method for sell exits", "Sell Exit");

		_sellClosePrice = Param(nameof(SellClosePrice), MaPriceTypes.Close)
			.SetDisplay("Sell Close Price", "Price type supplied to the sell exit MA", "Sell Exit");

		_useBuy = Param(nameof(UseBuy), true)
			.SetDisplay("Use Buy", "Enable long trades", "General");

		_useSell = Param(nameof(UseSell), true)
			.SetDisplay("Use Sell", "Enable short trades", "General");

		_considerPriceLastOut = Param(nameof(ConsiderPriceLastOut), true)
			.SetDisplay("Consider Last Exit Price", "Require price improvement before re-entry", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles processed by the strategy", "General");
	}

	/// <summary>
	/// Risk per trade as a fraction of the portfolio equity.
	/// </summary>
	public decimal MaximumRisk
	{
		get => _maximumRisk.Value;
		set => _maximumRisk.Value = value;
	}

	/// <summary>
	/// Lot reduction factor after consecutive losing trades.
	/// </summary>
	public decimal DecreaseFactor
	{
		get => _decreaseFactor.Value;
		set => _decreaseFactor.Value = value;
	}

	/// <summary>
	/// Moving average period for buy entries.
	/// </summary>
	public int BuyOpenPeriod
	{
		get => _buyOpenPeriod.Value;
		set => _buyOpenPeriod.Value = value;
	}

	/// <summary>
	/// Shift in bars for the buy entry moving average.
	/// </summary>
	public int BuyOpenShift
	{
		get => _buyOpenShift.Value;
		set => _buyOpenShift.Value = value;
	}

	/// <summary>
	/// Moving average method for buy entries.
	/// </summary>
	public MaMethods BuyOpenMethod
	{
		get => _buyOpenMethod.Value;
		set => _buyOpenMethod.Value = value;
	}

	/// <summary>
	/// Price type used for the buy entry moving average.
	/// </summary>
	public MaPriceTypes BuyOpenPrice
	{
		get => _buyOpenPrice.Value;
		set => _buyOpenPrice.Value = value;
	}

	/// <summary>
	/// Moving average period for buy exits.
	/// </summary>
	public int BuyClosePeriod
	{
		get => _buyClosePeriod.Value;
		set => _buyClosePeriod.Value = value;
	}

	/// <summary>
	/// Shift in bars for the buy exit moving average.
	/// </summary>
	public int BuyCloseShift
	{
		get => _buyCloseShift.Value;
		set => _buyCloseShift.Value = value;
	}

	/// <summary>
	/// Moving average method for buy exits.
	/// </summary>
	public MaMethods BuyCloseMethod
	{
		get => _buyCloseMethod.Value;
		set => _buyCloseMethod.Value = value;
	}

	/// <summary>
	/// Price type used for the buy exit moving average.
	/// </summary>
	public MaPriceTypes BuyClosePrice
	{
		get => _buyClosePrice.Value;
		set => _buyClosePrice.Value = value;
	}

	/// <summary>
	/// Moving average period for sell entries.
	/// </summary>
	public int SellOpenPeriod
	{
		get => _sellOpenPeriod.Value;
		set => _sellOpenPeriod.Value = value;
	}

	/// <summary>
	/// Shift in bars for the sell entry moving average.
	/// </summary>
	public int SellOpenShift
	{
		get => _sellOpenShift.Value;
		set => _sellOpenShift.Value = value;
	}

	/// <summary>
	/// Moving average method for sell entries.
	/// </summary>
	public MaMethods SellOpenMethod
	{
		get => _sellOpenMethod.Value;
		set => _sellOpenMethod.Value = value;
	}

	/// <summary>
	/// Price type used for the sell entry moving average.
	/// </summary>
	public MaPriceTypes SellOpenPrice
	{
		get => _sellOpenPrice.Value;
		set => _sellOpenPrice.Value = value;
	}

	/// <summary>
	/// Moving average period for sell exits.
	/// </summary>
	public int SellClosePeriod
	{
		get => _sellClosePeriod.Value;
		set => _sellClosePeriod.Value = value;
	}

	/// <summary>
	/// Shift in bars for the sell exit moving average.
	/// </summary>
	public int SellCloseShift
	{
		get => _sellCloseShift.Value;
		set => _sellCloseShift.Value = value;
	}

	/// <summary>
	/// Moving average method for sell exits.
	/// </summary>
	public MaMethods SellCloseMethod
	{
		get => _sellCloseMethod.Value;
		set => _sellCloseMethod.Value = value;
	}

	/// <summary>
	/// Price type used for the sell exit moving average.
	/// </summary>
	public MaPriceTypes SellClosePrice
	{
		get => _sellClosePrice.Value;
		set => _sellClosePrice.Value = value;
	}

	/// <summary>
	/// Enable long trades.
	/// </summary>
	public bool UseBuy
	{
		get => _useBuy.Value;
		set => _useBuy.Value = value;
	}

	/// <summary>
	/// Enable short trades.
	/// </summary>
	public bool UseSell
	{
		get => _useSell.Value;
		set => _useSell.Value = value;
	}

	/// <summary>
	/// Require price improvement relative to the last exit before re-entering.
	/// </summary>
	public bool ConsiderPriceLastOut
	{
		get => _considerPriceLastOut.Value;
		set => _considerPriceLastOut.Value = value;
	}

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

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

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

		_buyOpenBuffer.Clear();
		_buyCloseBuffer.Clear();
		_sellOpenBuffer.Clear();
		_sellCloseBuffer.Clear();

		_lastExitPrice = 0m;
		_lastEntryPrice = 0m;
		_lastEntrySide = null;
		_signedPosition = 0m;
		_consecutiveLosses = 0;
	}

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

		_buyOpenMa = CreateMovingAverage(BuyOpenMethod, BuyOpenPeriod);
		_buyCloseMa = CreateMovingAverage(BuyCloseMethod, BuyClosePeriod);
		_sellOpenMa = CreateMovingAverage(SellOpenMethod, SellOpenPeriod);
		_sellCloseMa = CreateMovingAverage(SellCloseMethod, SellClosePeriod);

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

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var buyOpen = ProcessMovingAverage(_buyOpenMa, _buyOpenBuffer, BuyOpenShift, GetPrice(candle, BuyOpenPrice), candle);
		var buyClose = ProcessMovingAverage(_buyCloseMa, _buyCloseBuffer, BuyCloseShift, GetPrice(candle, BuyClosePrice), candle);
		var sellOpen = ProcessMovingAverage(_sellOpenMa, _sellOpenBuffer, SellOpenShift, GetPrice(candle, SellOpenPrice), candle);
		var sellClose = ProcessMovingAverage(_sellCloseMa, _sellCloseBuffer, SellCloseShift, GetPrice(candle, SellClosePrice), candle);

		if (buyOpen is not decimal buyOpenValue ||
			buyClose is not decimal buyCloseValue ||
			sellOpen is not decimal sellOpenValue ||
			sellClose is not decimal sellCloseValue)
		{
			return;
		}


		if (Position != 0)
		{
			ProcessCloseSignal(candle, buyCloseValue, sellCloseValue);
		}
		else
		{
			ProcessOpenSignal(candle, buyOpenValue, sellOpenValue);
		}
	}

	private void ProcessOpenSignal(ICandleMessage candle, decimal buyMa, decimal sellMa)
	{
		var openPrice = candle.OpenPrice;
		var closePrice = candle.ClosePrice;

		if (UseBuy && openPrice < buyMa && closePrice > buyMa && CanReEnter(Sides.Buy, closePrice))
		{
			var volume = CalculateTradeVolume(closePrice);
			if (volume > 0)
			{
				BuyMarket(volume);
				this.AddInfoLog($"Buy signal. Close={closePrice}, MA={buyMa}, Volume={volume}");
			}
		}
		else if (UseSell && openPrice > sellMa && closePrice < sellMa && CanReEnter(Sides.Sell, closePrice))
		{
			var volume = CalculateTradeVolume(closePrice);
			if (volume > 0)
			{
				SellMarket(volume);
				this.AddInfoLog($"Sell signal. Close={closePrice}, MA={sellMa}, Volume={volume}");
			}
		}
	}

	private void ProcessCloseSignal(ICandleMessage candle, decimal buyMa, decimal sellMa)
	{
		var openPrice = candle.OpenPrice;
		var closePrice = candle.ClosePrice;

		if (Position > 0 && openPrice > buyMa && closePrice < buyMa)
		{
			if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
			this.AddInfoLog($"Close long. Close={closePrice}, MA={buyMa}");
		}
		else if (Position < 0 && openPrice < sellMa && closePrice > sellMa)
		{
			if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(-Position);
			this.AddInfoLog($"Close short. Close={closePrice}, MA={sellMa}");
		}
	}

	private bool CanReEnter(Sides side, decimal price)
	{
		if (!ConsiderPriceLastOut)
			return true;

		if (_lastExitPrice == 0m)
			return true;

		return side == Sides.Buy
			? _lastExitPrice >= price
			: _lastExitPrice <= price;
	}

	private decimal? ProcessMovingAverage(DecimalLengthIndicator indicator, Queue<decimal> buffer, int shift, decimal price, ICandleMessage candle)
	{
		if (indicator == null)
			return null;

		var value = indicator.Process(new DecimalIndicatorValue(indicator, price, candle.OpenTime) { IsFinal = true });

		if (!indicator.IsFormed)
			return null;

		var maValue = value.ToDecimal();

		buffer.Enqueue(maValue);
		var maxSize = shift + 1;
		while (buffer.Count > maxSize)
			buffer.Dequeue();

		if (buffer.Count < maxSize)
			return null;

		return shift == 0 ? maValue : buffer.Peek();
	}

	private decimal CalculateTradeVolume(decimal price)
	{
		var baseVolume = Volume > 0 ? Volume : 1m;

		if (price <= 0)
			return NormalizeVolume(baseVolume);

		var equity = Portfolio?.BeginValue ?? 0m;
		if (equity <= 0)
			return NormalizeVolume(baseVolume);

		var volume = equity * MaximumRisk / price;

		if (DecreaseFactor > 0 && _consecutiveLosses > 1)
		{
			var reduction = volume * _consecutiveLosses / DecreaseFactor;
			volume -= reduction;
		}

		if (volume <= 0)
			volume = baseVolume;

		return NormalizeVolume(volume);
	}

	private decimal NormalizeVolume(decimal volume)
	{
		var security = Security;
		if (security != null)
		{
			var step = security.VolumeStep ?? 1m;
			if (step <= 0)
				step = 1m;

			if (volume < step)
				volume = step;

			var steps = Math.Floor(volume / step);
			if (steps < 1m)
				steps = 1m;

			volume = steps * step;
		}

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

		return volume;
	}

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

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

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);

		var volume = trade.Trade.Volume;
		if (volume <= 0)
			return;

		var delta = trade.Order.Side == Sides.Buy ? volume : -volume;
		var previousPosition = _signedPosition;
		_signedPosition += delta;

		if (previousPosition == 0m && _signedPosition != 0m)
		{
			_lastEntrySide = delta > 0m ? Sides.Buy : Sides.Sell;
			_lastEntryPrice = trade.Trade.Price;
		}
		else if (previousPosition != 0m && _signedPosition == 0m)
		{
			_lastExitPrice = trade.Trade.Price;

			if (_lastEntrySide != null && _lastEntryPrice != 0m)
			{
				var profit = _lastEntrySide == Sides.Buy
					? _lastExitPrice - _lastEntryPrice
					: _lastEntryPrice - _lastExitPrice;

				if (profit > 0m)
				{
					_consecutiveLosses = 0;
				}
				else if (profit < 0m)
				{
					_consecutiveLosses++;
				}
			}

			_lastEntrySide = null;
			_lastEntryPrice = 0m;
		}
	}
}