Открыть на GitHub

Стратегия EA Moving Average

Обзор

  • Конвертация советника MetaTrader "EA Moving Average" (редакция barabashkakvn).
  • Использует четыре независимые скользящие средние для сигналов входа и выхода в лонг и шорт.
  • Рассчитана на один инструмент в неттинговом режиме. По умолчанию используется таймфрейм 15 минут, но можно выбрать любой тип свечей.
  • В стратегии одновременно открыта только одна позиция. Пока позиция активна, проверяются только условия выхода.

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

Вход в лонг

  1. Текущая свеча должна закрыться выше скользящей Buy Open, открывшись ниже неё (кроссовер внутри одной свечи).
  2. Параметр UseBuy должен быть включён.
  3. Если ConsiderPriceLastOut включён, текущая цена должна быть меньше или равна цене последнего закрытия позиции. Это защищает от покупки выше предыдущего выхода.
  4. При выполнении условий отправляется рыночная заявка на покупку с объёмом, рассчитанным моделью риска.

Выход из лонга

  1. Актуален только при открытой длинной позиции.
  2. Свеча должна открыться выше Buy Close и закрыться ниже неё, что даёт медвежий кроссовер.
  3. Вся позиция закрывается рыночным ордером.

Вход в шорт

  1. Свеча должна закрыться ниже скользящей Sell Open после открытия выше неё.
  2. Параметр UseSell должен быть включён.
  3. При активном ConsiderPriceLastOut текущая цена должна быть больше или равна цене последнего выхода, чтобы не продавать ниже предыдущего покрытия.
  4. Отправляется рыночная заявка на продажу с объёмом по модели риска.

Выход из шорта

  1. Актуален только при короткой позиции.
  2. Свеча должна открыться ниже Sell Close и закрыться выше неё.
  3. Короткая позиция полностью закрывается по рынку.

Риск и размер позиции

  • MaximumRisk задаёт долю капитала, которой стратегия готова рискнуть в одной сделке. Значение умножается на стоимость портфеля и делится на текущую цену инструмента, формируя базовый объём.
  • DecreaseFactor повторяет уменьшение лота из оригинального советника. После двух и более убыточных сделок подряд объём сокращается пропорционально серии убытков, делённой на DecreaseFactor.
  • Итоговый объём приводится к шагу объёма инструмента и не опускается ниже одного шага. При сбое расчёта используется свойство Volume стратегии (по умолчанию 1 контракт/лот).

Параметры

Параметр Значение по умолчанию Описание
MaximumRisk 0.02 Доля капитала, которой рискуем в сделке.
DecreaseFactor 3 Коэффициент уменьшения объёма после серии убытков (0 отключает).
BuyOpenPeriod 30 Период скользящей средней для входа в лонг.
BuyOpenShift 3 Сдвиг (в барах) скользящей для входа в лонг.
BuyOpenMethod Exponential Метод скользящей средней для входа в лонг (Simple, Exponential, Smoothed, LinearWeighted).
BuyOpenPrice Close Тип цены для расчёта скользящей входа в лонг.
BuyClosePeriod 14 Период скользящей средней выхода из лонга.
BuyCloseShift 3 Сдвиг (в барах) скользящей выхода из лонга.
BuyCloseMethod Exponential Метод скользящей средней выхода из лонга.
BuyClosePrice Close Тип цены для выхода из лонга.
SellOpenPeriod 30 Период скользящей для входа в шорт.
SellOpenShift 0 Сдвиг (в барах) скользящей входа в шорт.
SellOpenMethod Exponential Метод скользящей для входа в шорт.
SellOpenPrice Close Тип цены для входа в шорт.
SellClosePeriod 20 Период скользящей выхода из шорта.
SellCloseShift 2 Сдвиг (в барах) скользящей выхода из шорта.
SellCloseMethod Exponential Метод скользящей выхода из шорта.
SellClosePrice Close Тип цены для выхода из шорта.
UseBuy true Разрешить длинные сделки.
UseSell true Разрешить короткие сделки.
ConsiderPriceLastOut true Требовать улучшение цены относительно последнего выхода.
CandleType Свечи 15 минут Тип свечей для расчётов.

Дополнительные замечания

  • Цена последнего выхода и счётчик убыточных сделок берутся из фактических исполнений, что соответствует поведению в MetaTrader.
  • В StockSharp сигналы обрабатываются на завершённых свечах, поэтому фильтр цены сравнивает закрытие свечи с последним выходом, что аппроксимирует проверку по ask/bid в исходном коде.
  • Стратегия рассчитана на неттинг; одновременные разнонаправленные позиции не поддерживаются.
  • Перед использованием на реальном счёте рекомендуется протестировать стратегию на исторических данных.
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;
		}
	}
}