GitHub で見る

ナツセコプロトレーダー4H戦略

概要

Natuseko Protrader 4H 戦略は、MetaTrader 4 エキスパート アドバイザー NatusekoProtrader4HStrategy の StockSharp 移植です。オリジナル ロボットは、指数移動平均、Bollinger バンドでフィルタリングされた MACD オシレーター、RSI しきい値、および Parabolic SAR を組み合わせて、 4時間足の強いブレイクアウトローソク足を特定します。適格なローソクが現れると、システムはすぐに開くか、 高速の EMA への引き戻しを待ってから入ります。一度配置されると、戦略は部分的な利益確定と完全な撤退を実行します。 RSI および Parabolic SAR 信号に基づいて、MQL コードに存在する資金管理ブロックを複製します。

取引ロジック

  1. CandleType で定義されたプライマリ ローソク足ストリーム (デフォルトでは 4 時間足のローソク足) をサブスクライブし、完了したローソク足のみを処理します。
  2. 終値の 3 つの指数移動平均 (高速、低速、トレンド) を計算します。 3 つすべての長さを設定できます。
  3. MACD インジケーター (EA から取得した高速、低速、シグナル期間) をフィードし、単純移動平均と Bollinger バンドを適用します。 MACD 本線。 Bollinger の正中線は、MQL バージョンで使用される基準レベルとして機能します。
  4. 完全なローソク足データを使用して、終値の RSI と Parabolic SAR を計算します。これらのインジケーターはエントリーとエグジットの両方を推進します。
  5. 次の条件がすべて当てはまる場合に、強気のセットアップローソク足を検出します。
    • 高速 EMA は低速とトレンドの EMA を上回っています。
    • RSI は RsiEntryLevel より上ですが、RsiTakeProfitLong より下です。
    • MACD の主線は、短い SMA と Bollinger の正中線の両方の上にあります。 SMA も正中線より上にあります。
    • ローソク本体は両方の影よりも大きいため、ローソクは動きの方向に強く閉じることになります。
    • Parabolic SAR はローソクの終値の下に座っています。
  6. 対称チェックを使用して弱気セットアップを検出します (以下の高速 EMA、RsiTakeProfitShortRsiEntryLevel の間の RSI、MACD の値) Bollinger 正中線の下、弱気のローソク体、終値の上の SAR)。
  7. 適格なローソク足がトレンド EMA から遠すぎる場合 (DistanceThresholdPoints を超える距離)、保留フラグを設定し、 引き戻し。 RSIとSARが強気シナリオに沿ったままである間に、価格が速いEMAに達するとロングエントリーがトリガーされます。の ショートエントリーは、下からの高速な EMA への引き戻しと同様に機能します。
  8. プルバックが必要ない場合、この戦略は反対のエクスポージャーをクローズし、TradeVolume ロットで新しいポジションをオープンします。ストップロス 配置は EA ルールに従います。UseSarStopLoss が有効な場合は Parabolic SAR が最優先され、それ以外の場合はトレンドが優先されます。 EMA が使用されます。 StopOffsetPoints は商品価格ステップで価格距離に変換され、ストップレベルに適用されます。
  9. ロングポジションがオープンしている間、ストラテジーは継続的にストップ価格を再計算し、エグジットを管理します。
    • 価格がストップを下回った場合、ポジション全体がクローズされます。
    • 少なくとも MinimumProfitPoints の利益 (商品ポイント単位) に達した後、戦略はポジションの半分を閉じることができます。 RSI が RsiTakeProfitLong を超えるか、Parabolic SAR が価格を上回ったとき (UseRsiTakeProfit によって制御され、 UseSarTakeProfit)。
    • 十分な利益が得られ、RSI が RsiEntryLevel を下回ると、残りのロングエクスポージャーは終了します。
  10. ショート ポジションは同じルールを反映しており、RSI のしきい値が反転され、SAR チェックが価格に対して反転されます。

ポジション管理

  • 部分的なエグジットは取引サイドごとに最大 1 回発生します。ポジションの半分を閉じた後、ストラテジーは完全終了条件を待ちます。 (RSI がニュートラルレベルを通過するかストップロスヒット)。
  • ストップロス価格は、MQL ロジックとの整合性を保つために、最新の Parabolic SAR またはトレンド EMA の値を使用してローソク足ごとに再計算されます。
  • ポジションサイズがゼロに戻ると、内部状態 (保留エントリーフラグ、ストップ参照、部分出口マーカー) がリセットされるため、 次の取引はきれいに始まります。

パラメーター

名前 種類 デフォルト 説明
CandleType DataType 4時間枠 戦略によって処理される主な時間枠。
TradeVolume decimal 0.1 エントリーに使用される注文量。
FastEmaPeriod int 13 高速 EMA フィルターの長さ。
SlowEmaPeriod int 21 低速な EMA フィルターの長さ。
TrendEmaPeriod int 55 EMA は距離チェックとストップロスの配置に使用されます。
MacdFastPeriod int 5 MACD インジケーター内の高速な EMA の長さ。
MacdSlowPeriod int 200 MACD インジケーター内の EMA の長さが遅いです。
MacdSignalPeriod int 1 MACD インジケーター内のシグナル移動平均の長さ。
BollingerPeriod int 20 Bollinger バンドの計算に使用される MACD サンプルの数。
BollingerWidth decimal 1 MACD Bollinger バンドの標準偏差乗数。
MacdSmaPeriod int 3 MACD スムージング SMA の長さ。
RsiPeriod int 21 RSI インジケーターの長さ。
RsiEntryLevel decimal 50 開始ルールと終了ルールによって共有される中立の RSI しきい値。
RsiTakeProfitLong decimal 65 ロングポジションの部分利益確定を可能にするRSIレベル。
RsiTakeProfitShort decimal 35 RSI レベルでショートポジションの部分利益確定が可能になります。
DistanceThresholdPoints decimal 100 エントリーが遅れるまでの、価格とトレンド EMA の間の商品ポイント単位の最大距離。
SarStep decimal 0.02 Parabolic SAR の加速ステップ。
SarMaximum decimal 0.2 Parabolic SAR の最大加速度。
UseSarStopLoss bool false Parabolic SAR を使用して保護停止を取得します。
UseTrendStopLoss bool true トレンド EMA を使用して保護ストップを導き出します。
StopOffsetPoints int 0 追加のオフセット (ポイント単位) が保護ストップ価格に追加されます。
UseSarTakeProfit bool true 価格が Parabolic SAR を超えたときに部分的な決済を有効にします。
UseRsiTakeProfit bool true RSI がテイクプロフィットしきい値に達したときに、部分的なエグジットを有効にします。
MinimumProfitPoints decimal 5 部分的または完全な利益確定ルールが有効になる前の最小利益 (ポイント単位)。

オリジナルの EA との違い

  • StockSharp はネットポジションを取引します。 MetaTrader のシングルチケットの動作をエミュレートするために、戦略は反対のチケットを自動的にクローズします 反対方向に新しい取引を開始する前にエクスポージャーを確認してください。
  • StockSharp が管理しないため、資金管理ヘルパーは個々の注文を変更するのではなく成行注文で実装されます。 チケットごとに停車します。この効果は EA と一致します。1 回の部分的な終了に続いて、RSI の勢いが弱まったときに最終的な終了が続きます。
  • 価格距離の計算は商品 PriceStep に依存します。証券が価格ステップを定義していない場合、戦略は次のように仮定します。 ステップ 1. 異なるポイント サイズを使用する楽器に応じて、DistanceThresholdPointsMinimumProfitPoints を調整します。

使い方のヒント

  • 機器のロットステップに従って TradeVolume を設定します。コンストラクターは同じ値を Strategy.Volume にも割り当てます。 ヘルパー メソッドは予想されるサイズを使用します。
  • ローソク足がトレンド EMA から遠く離れて終了するために取引が頻繁に遅れる場合は、DistanceThresholdPoints を下げるか、フィルターを無効にしてください それをゼロに設定します。
  • 戦略をチャート化することをお勧めします。コードはローソク足、3 つの EMA、RSI、Parabolic SAR、および MACD Bollinger バンドを描画します。 変換されたロジックを視覚的に確認します。
  • MACD パラメータは、EA の珍しい組み合わせ (fast=5、slow=200、signal=1) を反映しています。本番稼働前に最適化を検討してください これは、このような広い低速期間により、非常に滑らかではあるものの遅れのある値が生成されるためです。
namespace StockSharp.Samples.Strategies;

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;

public class NatusekoProtrader4HStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<int> _trendEmaPeriod;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;
	private readonly StrategyParam<int> _macdSmaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiEntryLevel;
	private readonly StrategyParam<decimal> _rsiTakeProfitLong;
	private readonly StrategyParam<decimal> _rsiTakeProfitShort;
	private readonly StrategyParam<decimal> _distanceThresholdPoints;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMaximum;
	private readonly StrategyParam<bool> _useSarStopLoss;
	private readonly StrategyParam<bool> _useTrendStopLoss;
	private readonly StrategyParam<int> _stopOffsetPoints;
	private readonly StrategyParam<bool> _useSarTakeProfit;
	private readonly StrategyParam<bool> _useRsiTakeProfit;
	private readonly StrategyParam<decimal> _minimumProfitPoints;

	private ExponentialMovingAverage _fastEma;
	private ExponentialMovingAverage _slowEma;
	private ExponentialMovingAverage _trendEma;
	private MovingAverageConvergenceDivergence _macd;
	private BollingerBands _macdBands;
	private SimpleMovingAverage _macdSma;
	private RelativeStrengthIndex _rsi;
	private ParabolicSar _parabolicSar;

	private bool _waitingForLongEntry;
	private bool _waitingForShortEntry;
	private bool _longPartialExecuted;
	private bool _shortPartialExecuted;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;

	public NatusekoProtrader4HStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle type", "Primary timeframe processed by the strategy.", "General");

		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade volume", "Default order size used for entries.", "Trading");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 13)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA period", "Length of the fast EMA filter.", "Indicator");

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA period", "Length of the slower EMA filter.", "Indicator");

		_trendEmaPeriod = Param(nameof(TrendEmaPeriod), 55)
			.SetGreaterThanZero()
			.SetDisplay("Trend EMA period", "Length of the trend EMA used for filters and stop loss.", "Indicator");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("MACD fast period", "Fast EMA length inside the MACD indicator.", "Indicator");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("MACD slow period", "Slow EMA length inside the MACD indicator.", "Indicator");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 1)
			.SetGreaterThanZero()
			.SetDisplay("MACD signal period", "Signal moving average length for MACD.", "Indicator");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger period", "Number of MACD samples used for the Bollinger Bands.", "Indicator");

		_bollingerWidth = Param(nameof(BollingerWidth), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger width", "Standard deviation multiplier for the MACD Bollinger Bands.", "Indicator");

		_macdSmaPeriod = Param(nameof(MacdSmaPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("MACD SMA period", "Length of the moving average applied to the MACD line.", "Indicator");

		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI period", "Length of the RSI filter.", "Indicator");

		_rsiEntryLevel = Param(nameof(RsiEntryLevel), 50m)
			.SetDisplay("RSI neutral level", "Central RSI threshold used for both entry and exit rules.", "Trading");

		_rsiTakeProfitLong = Param(nameof(RsiTakeProfitLong), 65m)
			.SetDisplay("RSI take profit long", "RSI value that triggers a partial exit for long positions.", "Trading");

		_rsiTakeProfitShort = Param(nameof(RsiTakeProfitShort), 35m)
			.SetDisplay("RSI take profit short", "RSI value that triggers a partial exit for short positions.", "Trading");

		_distanceThresholdPoints = Param(nameof(DistanceThresholdPoints), 100m)
			.SetNotNegative()
			.SetDisplay("Distance threshold", "Maximum distance in points between price and the trend EMA before delaying the entry.", "Trading");

		_sarStep = Param(nameof(SarStep), 0.02m)
			.SetGreaterThanZero()
			.SetDisplay("SAR step", "Acceleration step of the Parabolic SAR indicator.", "Indicator");

		_sarMaximum = Param(nameof(SarMaximum), 0.2m)
			.SetGreaterThanZero()
			.SetDisplay("SAR maximum", "Maximum acceleration of the Parabolic SAR indicator.", "Indicator");

		_useSarStopLoss = Param(nameof(UseSarStopLoss), false)
			.SetDisplay("Use SAR stop loss", "Whether the Parabolic SAR defines the protective stop price.", "Risk");

		_useTrendStopLoss = Param(nameof(UseTrendStopLoss), true)
			.SetDisplay("Use trend stop loss", "Whether the trend EMA defines the protective stop price.", "Risk");

		_stopOffsetPoints = Param(nameof(StopOffsetPoints), 0)
			.SetNotNegative()
			.SetDisplay("Stop offset", "Additional point offset added to the protective stop.", "Risk");

		_useSarTakeProfit = Param(nameof(UseSarTakeProfit), true)
			.SetDisplay("Use SAR take profit", "Enable partial exits when price crosses the Parabolic SAR.", "Risk");

		_useRsiTakeProfit = Param(nameof(UseRsiTakeProfit), true)
			.SetDisplay("Use RSI take profit", "Enable partial exits driven by RSI thresholds.", "Risk");

		_minimumProfitPoints = Param(nameof(MinimumProfitPoints), 5m)
			.SetNotNegative()
			.SetDisplay("Minimum profit", "Minimum profit in points required before take-profit rules activate.", "Risk");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

	public int TrendEmaPeriod
	{
		get => _trendEmaPeriod.Value;
		set => _trendEmaPeriod.Value = value;
	}

	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	public int MacdSmaPeriod
	{
		get => _macdSmaPeriod.Value;
		set => _macdSmaPeriod.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiEntryLevel
	{
		get => _rsiEntryLevel.Value;
		set => _rsiEntryLevel.Value = value;
	}

	public decimal RsiTakeProfitLong
	{
		get => _rsiTakeProfitLong.Value;
		set => _rsiTakeProfitLong.Value = value;
	}

	public decimal RsiTakeProfitShort
	{
		get => _rsiTakeProfitShort.Value;
		set => _rsiTakeProfitShort.Value = value;
	}

	public decimal DistanceThresholdPoints
	{
		get => _distanceThresholdPoints.Value;
		set => _distanceThresholdPoints.Value = value;
	}

	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}

	public decimal SarMaximum
	{
		get => _sarMaximum.Value;
		set => _sarMaximum.Value = value;
	}

	public bool UseSarStopLoss
	{
		get => _useSarStopLoss.Value;
		set => _useSarStopLoss.Value = value;
	}

	public bool UseTrendStopLoss
	{
		get => _useTrendStopLoss.Value;
		set => _useTrendStopLoss.Value = value;
	}

	public int StopOffsetPoints
	{
		get => _stopOffsetPoints.Value;
		set => _stopOffsetPoints.Value = value;
	}

	public bool UseSarTakeProfit
	{
		get => _useSarTakeProfit.Value;
		set => _useSarTakeProfit.Value = value;
	}

	public bool UseRsiTakeProfit
	{
		get => _useRsiTakeProfit.Value;
		set => _useRsiTakeProfit.Value = value;
	}

	public decimal MinimumProfitPoints
	{
		get => _minimumProfitPoints.Value;
		set => _minimumProfitPoints.Value = value;
	}

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

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

		_waitingForLongEntry = false;
		_waitingForShortEntry = false;
		_longPartialExecuted = false;
		_shortPartialExecuted = false;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;
	}

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

		Volume = TradeVolume;

		_fastEma = new EMA { Length = FastEmaPeriod };
		_slowEma = new EMA { Length = SlowEmaPeriod };
		_trendEma = new EMA { Length = TrendEmaPeriod };

		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = MacdFastPeriod },
			LongMa = { Length = MacdSlowPeriod }
		};

		_macdBands = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerWidth
		};

		_macdSma = new SMA { Length = MacdSmaPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_parabolicSar = new ParabolicSar
		{
			Acceleration = SarStep,
			AccelerationMax = SarMaximum
		};

		_waitingForLongEntry = false;
		_waitingForShortEntry = false;
		_longPartialExecuted = false;
		_shortPartialExecuted = false;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longEntryPrice = null;
		_shortEntryPrice = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(new IIndicator[] { _fastEma, _slowEma, _trendEma, _rsi, _macd, _parabolicSar }, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastEma);
			DrawIndicator(area, _slowEma);
			DrawIndicator(area, _trendEma);
			DrawIndicator(area, _rsi);
			DrawIndicator(area, _parabolicSar);
			DrawOwnTrades(area);
		}

		var macdArea = CreateChartArea();
		if (macdArea != null)
		{
			DrawIndicator(macdArea, _macd);
			DrawIndicator(macdArea, _macdBands);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var fastEmaVal = values[0];
		var slowEmaVal = values[1];
		var trendEmaVal = values[2];
		var rsiVal = values[3];
		var macdVal = values[4];
		var sarVal = values[5];

		if (fastEmaVal.IsEmpty || slowEmaVal.IsEmpty || trendEmaVal.IsEmpty ||
			rsiVal.IsEmpty || macdVal.IsEmpty || sarVal.IsEmpty)
			return;

		var fastEmaValue = fastEmaVal.ToDecimal();
		var slowEmaValue = slowEmaVal.ToDecimal();
		var trendEmaValue = trendEmaVal.ToDecimal();
		var rsiValue = rsiVal.ToDecimal();
		var macdLine = macdVal.ToDecimal();
		var sarValue = sarVal.ToDecimal();

		var macdSmaRaw = _macdSma.Process(macdLine, candle.OpenTime, true);
		if (macdSmaRaw.IsEmpty)
			return;
		var macdSmaValue = macdSmaRaw.ToDecimal();
		var macdMiddle = macdSmaValue;

		if (!_fastEma.IsFormed || !_slowEma.IsFormed || !_trendEma.IsFormed || !_macd.IsFormed || !_macdSma.IsFormed ||
				!_rsi.IsFormed || !_parabolicSar.IsFormed)
		{
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var priceStep = Security?.PriceStep ?? 0m;
		if (priceStep <= 0m)
			priceStep = 1m;
		var stopOffset = StopOffsetPoints * priceStep;

		if (Position > 0)
		{
			var updatedStop = CalculateStopPrice(true, sarValue, trendEmaValue, stopOffset);
			if (updatedStop.HasValue)
				_longStopPrice = updatedStop;
		}
		else if (Position < 0)
		{
			var updatedStop = CalculateStopPrice(false, sarValue, trendEmaValue, stopOffset);
			if (updatedStop.HasValue)
				_shortStopPrice = updatedStop;
		}

		ManageOpenPositions(candle, rsiValue, sarValue, priceStep);

		if (Position > 0)
			_waitingForLongEntry = false;
		if (Position < 0)
			_waitingForShortEntry = false;

		TryEnterLong(candle, fastEmaValue, slowEmaValue, trendEmaValue, rsiValue, macdLine, macdSmaValue, macdMiddle, sarValue, priceStep);
		TryEnterShort(candle, fastEmaValue, slowEmaValue, trendEmaValue, rsiValue, macdLine, macdSmaValue, macdMiddle, sarValue, priceStep);
	}

	private void ManageOpenPositions(ICandleMessage candle, decimal rsiValue, decimal sarValue, decimal priceStep)
	{
		var profitThreshold = MinimumProfitPoints * priceStep;

		if (Position > 0)
		{
			var volume = Math.Abs(Position);
			if (volume > 0m)
			{
				if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
				{
					SellMarket(volume);
					ResetLongState();
				}
				else
				{
					var entry = _longEntryPrice ?? candle.ClosePrice;
					_longEntryPrice ??= entry;

					var profit = candle.ClosePrice - entry;

					if ((profitThreshold <= 0m || profit >= profitThreshold) && !_longPartialExecuted)
					{
						if (UseRsiTakeProfit && rsiValue >= RsiTakeProfitLong)
						{
							CloseHalfLong(volume);
						}
						else if (UseSarTakeProfit && sarValue >= candle.ClosePrice)
						{
							CloseHalfLong(volume);
						}
					}

					if ((profitThreshold <= 0m || profit >= profitThreshold) && rsiValue <= RsiEntryLevel)
					{
						SellMarket(volume);
						ResetLongState();
					}
				}
			}
		}
		else
		{
			ResetLongState();
		}

		if (Position < 0)
		{
			var volume = Math.Abs(Position);
			if (volume > 0m)
			{
				if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
				{
					BuyMarket(volume);
					ResetShortState();
				}
				else
				{
					var entry = _shortEntryPrice ?? candle.ClosePrice;
					_shortEntryPrice ??= entry;

					var profit = entry - candle.ClosePrice;

					if ((profitThreshold <= 0m || profit >= profitThreshold) && !_shortPartialExecuted)
					{
						if (UseRsiTakeProfit && rsiValue <= RsiTakeProfitShort)
						{
							CloseHalfShort(volume);
						}
						else if (UseSarTakeProfit && sarValue <= candle.ClosePrice)
						{
							CloseHalfShort(volume);
						}
					}

					if ((profitThreshold <= 0m || profit >= profitThreshold) && rsiValue >= RsiEntryLevel)
					{
						BuyMarket(volume);
						ResetShortState();
					}
				}
			}
		}
		else
		{
			ResetShortState();
		}
	}

	private void CloseHalfLong(decimal volume)
	{
		var half = volume / 2m;
		if (half <= 0m)
			return;

		SellMarket(half);
		_longPartialExecuted = true;
	}

	private void CloseHalfShort(decimal volume)
	{
		var half = volume / 2m;
		if (half <= 0m)
			return;

		BuyMarket(half);
		_shortPartialExecuted = true;
	}

	private void TryEnterLong(ICandleMessage candle, decimal fastEma, decimal slowEma, decimal trendEma, decimal rsi, decimal macdLine,
		decimal macdSma, decimal macdMiddle, decimal sar, decimal priceStep)
	{
		if (Position > 0)
			return;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var upperShadow = Math.Abs(candle.HighPrice - candle.ClosePrice);
		var lowerShadow = Math.Abs(candle.OpenPrice - candle.LowPrice);

		var baseCondition = fastEma > slowEma && fastEma > trendEma && rsi > RsiEntryLevel &&
			macdLine > 0m;

		if (baseCondition)
		{
			var distanceLimit = DistanceThresholdPoints * priceStep;
			var distance = candle.ClosePrice - trendEma;

			if (distanceLimit > 0m && distance >= distanceLimit)
			{
				_waitingForLongEntry = true;
			}
			else
			{
				OpenLong(candle, sar, trendEma, priceStep);
			}
		}
		else if (_waitingForLongEntry && candle.LowPrice <= fastEma && rsi < RsiTakeProfitLong && sar < candle.ClosePrice)
		{
			OpenLong(candle, sar, trendEma, priceStep);
			_waitingForLongEntry = false;
		}
	}

	private void TryEnterShort(ICandleMessage candle, decimal fastEma, decimal slowEma, decimal trendEma, decimal rsi, decimal macdLine,
		decimal macdSma, decimal macdMiddle, decimal sar, decimal priceStep)
	{
		if (Position < 0)
			return;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var upperShadow = Math.Abs(candle.HighPrice - candle.ClosePrice);
		var lowerShadow = Math.Abs(candle.OpenPrice - candle.LowPrice);

		var baseCondition = fastEma < slowEma && fastEma < trendEma && rsi < RsiEntryLevel &&
			macdLine < 0m;

		if (baseCondition)
		{
			var distanceLimit = DistanceThresholdPoints * priceStep;
			var distance = trendEma - candle.ClosePrice;

			if (distanceLimit > 0m && distance >= distanceLimit)
			{
				_waitingForShortEntry = true;
			}
			else
			{
				OpenShort(candle, sar, trendEma, priceStep);
			}
		}
		else if (_waitingForShortEntry && candle.HighPrice >= fastEma && rsi > RsiTakeProfitShort && sar > candle.ClosePrice)
		{
			OpenShort(candle, sar, trendEma, priceStep);
			_waitingForShortEntry = false;
		}
	}

	private void OpenLong(ICandleMessage candle, decimal sar, decimal trendEma, decimal priceStep)
	{
		var volume = TradeVolume;
		if (volume <= 0m)
			return;

		if (Position < 0m)
			BuyMarket(Math.Abs(Position));

		BuyMarket(volume);

		_longEntryPrice = candle.ClosePrice;
		_longPartialExecuted = false;

		_longStopPrice = CalculateStopPrice(true, sar, trendEma, StopOffsetPoints * priceStep);
	}

	private void OpenShort(ICandleMessage candle, decimal sar, decimal trendEma, decimal priceStep)
	{
		var volume = TradeVolume;
		if (volume <= 0m)
			return;

		if (Position > 0m)
			SellMarket(Math.Abs(Position));

		SellMarket(volume);

		_shortEntryPrice = candle.ClosePrice;
		_shortPartialExecuted = false;

		_shortStopPrice = CalculateStopPrice(false, sar, trendEma, StopOffsetPoints * priceStep);
	}

	private decimal? CalculateStopPrice(bool isLong, decimal sar, decimal trendEma, decimal offset)
	{
		decimal? stop = null;

		if (UseSarStopLoss)
			stop = isLong ? sar - offset : sar + offset;

		if (UseTrendStopLoss)
			stop = isLong ? trendEma - offset : trendEma + offset;

		return stop;
	}

	private void ResetLongState()
	{
		_longPartialExecuted = false;
		_longStopPrice = null;
		_longEntryPrice = null;
	}

	private void ResetShortState()
	{
		_shortPartialExecuted = false;
		_shortStopPrice = null;
		_shortEntryPrice = null;
	}
}