GitHub で見る

EA Moving Average戦略

概要

  • MetaTraderのエキスパートアドバイザー**「EA Moving Average」**(barabashkakvn版)から変換されました。
  • ロングとショートのエントリーとエグジットを制御するために4つの独立した移動平均線を使用します。
  • ネッティングモードの単一銘柄用に設計されています。デフォルトのキャンドルタイプは15分足の時間軸ですが、任意の通常のキャンドルタイプを選択できます。
  • 戦略は一度に最大1つのポジションを開きます。ポジションがアクティブの間は、エグジットルールのみが評価されます。

取引ロジック

ロングエントリー

  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はオリジナルのMetaTraderのロット削減をエミュレートします。2回以上の連続した負け取引の後、ボリュームは損失連敗をDecreaseFactorで割った値に比例して削減されます。
  • ボリュームは銘柄のボリュームステップに合わせられ、1ステップを下回ることはありません。リスク計算が失敗した場合のフォールバックは戦略のVolumeプロパティ(デフォルトで1契約/ロット)です。

パラメーター

パラメーター デフォルト 説明
MaximumRisk 0.02 取引ごとにリスクにさらされる資産の割合。
DecreaseFactor 3 連続した損失後のロット削減係数。0で無効化。
BuyOpenPeriod 30 ロングエントリーに使用する移動平均線の期間。
BuyOpenShift 3 ロングエントリー移動平均線に適用される前方シフト(バー)。
BuyOpenMethod Exponential ロングエントリーの移動平均メソッド(SimpleExponentialSmoothedLinearWeighted)。
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 15m足 計算に使用するキャンドルシリーズ。

追加の注意事項

  • 最後のエグジット価格と連続損失カウンターは取引執行から追跡され、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;
		}
	}
}