GitHub で見る

VR-ZVER v2 ストラテジー

VR-ZVER v2 ストラテジーは、クラシックなMetaTraderのエキスパートアドバイザーのStockSharpポートです。元のスクリプトのトリプル確認のアイデアを維持しています:すべてのトレードは移動平均、ストキャスティクスオシレーター、RSIによってサポートされなければなりません。すべての有効なフィルターが一致した時のみ、ストラテジーは成行注文を出します。

トレードロジック

  • シグナルはローソク足が閉じるときに評価されます。イントラバーの変動はストップやターゲットの検証にのみ使用されます。
  • MAフィルターが有効な場合、3つの指数移動平均(高速、低速、非常に低速)は同じ順序でスタックされてトレンドを検証する必要があります。
  • ストキャスティクスフィルターは、設定可能な上限と下限バンドの近くで%K/%Dのクロスオーバーを待ちます。
  • RSIフィルターは、オシレーターが中立ゾーンを離れることを要求します(ロングの場合は下限バンドを下回り、ショートの場合は上限バンドを上回る)。
  • シグナルは、すべての有効なフィルターが同じ方向に投票した場合のみ受け入れられます。いずれかのフィルターが同意しない場合、取引は行われません。
  • ストラテジーは一度に1つのポジションを開きます。ヘッジやグリッドの構築は行いません;フラットの場合、次の整合されたシグナルを待ちます。

ポジション管理

  • テイクプロフィットとストップロスはpipsで表されます。初期ストップは設定された距離の3分の2に設定され、元のEA動作を再現します。
  • ブレークイーブントリガー(pips単位)は、取引が指定された距離を得た後にストップをエントリー価格に移動します。
  • トレーリングストップは距離と追加ステップを使用します。ステップにより、ストップはすべての小さな上昇で更新されるのを防ぎ、MT5のトレーリングロジックに対応します。
  • ロングとショートトレードは同じ管理ルールを共有し、ローソク足の高値/安値に対して対称的に反応します。

ポジションサイジング

  • FixedVolumeがゼロより大きい場合、各注文を固定サイズで開きます。
  • FixedVolumeがゼロに設定されている場合、ストラテジーはRiskPercent、現在のポートフォリオ価値、ストップ距離からボリュームを計算します。価格ステップとステップ価格は、pips距離を金銭的リスクに変換するために使用されます。
  • ボリュームは、インスツルメントのVolumeMinVolumeMaxVolumeStepの制約を尊重するために丸められます。計算されたサイズが小さすぎる場合、注文はスキップされます。

パラメーター

名前 説明
CandleType シグナル生成に使用する時間軸(デフォルト15分ローソク足)。
FixedVolume, RiskPercent 固定またはリスクベースのサイジングを選択。
StopLossPips, TakeProfitPips pips単位の基本保護距離。
TrailingStopPips, TrailingStepPips, BreakevenPips トレード管理の閾値。
AllowLongs, AllowShorts 個別の方向を有効または無効化。
UseMovingAverageFilter, FastMaPeriod, SlowMaPeriod, VerySlowMaPeriod トリプルEMAトレンドフィルター。
UseStochastic, StochasticKPeriod, StochasticDPeriod, StochasticSmooth, StochasticUpperLevel, StochasticLowerLevel ストキャスティクス確認設定。
UseRsi, RsiPeriod, RsiUpperLevel, RsiLowerLevel RSI確認バンド。

注意事項

  • pip変換は元のEAをエミュレートします:5桁と3桁のシンボルは、pip値を計算する前に価格ステップを10倍にします。
  • StockSharpポートは成行注文のみを使用します。MetaTraderバージョンのロックと保留注文機能は、実装を高レベルAPIと一貫して保つために意図的に省略されています。
  • EMA、ストキャスティクス、RSIのオーバーレイを表示したい場合は、ストラテジーをチャートに添付してください;チャートエリアが利用可能な場合に自動的に描画されます。
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;

/// <summary>
/// Port of the VR-ZVER v2 expert advisor with triple EMA confirmation and stochastic/RSI filters.
/// </summary>
public class VrZverV2Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _fixedVolume;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _trailingStepPips;
	private readonly StrategyParam<decimal> _breakevenPips;
	private readonly StrategyParam<bool> _allowLongs;
	private readonly StrategyParam<bool> _allowShorts;
	private readonly StrategyParam<bool> _useMovingAverageFilter;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _verySlowMaPeriod;
	private readonly StrategyParam<bool> _useStochastic;
	private readonly StrategyParam<int> _stochasticKPeriod;
	private readonly StrategyParam<int> _stochasticDPeriod;
	private readonly StrategyParam<int> _stochasticSmooth;
	private readonly StrategyParam<decimal> _stochasticUpperLevel;
	private readonly StrategyParam<decimal> _stochasticLowerLevel;
	private readonly StrategyParam<bool> _useRsi;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiUpperLevel;
	private readonly StrategyParam<decimal> _rsiLowerLevel;

	private ExponentialMovingAverage _fastMa;
	private ExponentialMovingAverage _slowMa;
	private ExponentialMovingAverage _verySlowMa;
	private StochasticOscillator _stochastic;
	private RelativeStrengthIndex _rsi;

	private decimal _pipSize;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal? _trailingStop;
	private bool _breakevenActivated;

	public VrZverV2Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
		.SetDisplay("Candle Type", "Time frame for signal generation", "General");

		_fixedVolume = Param(nameof(FixedVolume), 1m)
		.SetDisplay("Fixed Volume", "Use fixed volume when greater than zero", "Risk");

		_riskPercent = Param(nameof(RiskPercent), 10m)
		.SetDisplay("Risk %", "Risk percentage used when fixed volume is zero", "Risk");

		_stopLossPips = Param(nameof(StopLossPips), 10000m)
		.SetDisplay("Stop Loss (pips)", "Full stop distance expressed in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 15000m)
		.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 8000m)
		.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");

		_trailingStepPips = Param(nameof(TrailingStepPips), 3000m)
		.SetDisplay("Trailing Step (pips)", "Additional distance before trailing updates", "Risk");

		_breakevenPips = Param(nameof(BreakevenPips), 5000m)
		.SetDisplay("Breakeven (pips)", "Move stop to entry after this profit", "Risk");

		_allowLongs = Param(nameof(AllowLongs), true)
		.SetDisplay("Allow Longs", "Permit buy trades", "General");

		_allowShorts = Param(nameof(AllowShorts), true)
		.SetDisplay("Allow Shorts", "Permit sell trades", "General");

		_useMovingAverageFilter = Param(nameof(UseMovingAverageFilter), true)
		.SetDisplay("Use MA Filter", "Require triple EMA alignment", "Indicators");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicators");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicators");

		_verySlowMaPeriod = Param(nameof(VerySlowMaPeriod), 7)
		.SetGreaterThanZero()
		.SetDisplay("Very Slow EMA", "Length of the very slow EMA", "Indicators");

		_useStochastic = Param(nameof(UseStochastic), false)
		.SetDisplay("Use Stochastic", "Enable stochastic confirmation", "Indicators");

		_stochasticKPeriod = Param(nameof(StochasticKPeriod), 42)
		.SetGreaterThanZero()
		.SetDisplay("Stochastic %K", "Number of periods for %K", "Indicators");

		_stochasticDPeriod = Param(nameof(StochasticDPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Stochastic %D", "Smoothing period for %D", "Indicators");

		_stochasticSmooth = Param(nameof(StochasticSmooth), 7)
		.SetGreaterThanZero()
		.SetDisplay("Stochastic Smooth", "Final smoothing for stochastic", "Indicators");

		_stochasticUpperLevel = Param(nameof(StochasticUpperLevel), 60m)
		.SetDisplay("Stochastic Upper", "Upper threshold for short signals", "Indicators");

		_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 40m)
		.SetDisplay("Stochastic Lower", "Lower threshold for long signals", "Indicators");

		_useRsi = Param(nameof(UseRsi), false)
		.SetDisplay("Use RSI", "Enable RSI filter", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "Length of the RSI", "Indicators");

		_rsiUpperLevel = Param(nameof(RsiUpperLevel), 60m)
		.SetDisplay("RSI Upper", "Upper threshold for short entries", "Indicators");

		_rsiLowerLevel = Param(nameof(RsiLowerLevel), 40m)
		.SetDisplay("RSI Lower", "Lower threshold for long entries", "Indicators");
	}

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

	public decimal FixedVolume
	{
		get => _fixedVolume.Value;
		set => _fixedVolume.Value = value;
	}

	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}

	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	public decimal TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

	public decimal BreakevenPips
	{
		get => _breakevenPips.Value;
		set => _breakevenPips.Value = value;
	}

	public bool AllowLongs
	{
		get => _allowLongs.Value;
		set => _allowLongs.Value = value;
	}

	public bool AllowShorts
	{
		get => _allowShorts.Value;
		set => _allowShorts.Value = value;
	}

	public bool UseMovingAverageFilter
	{
		get => _useMovingAverageFilter.Value;
		set => _useMovingAverageFilter.Value = value;
	}

	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	public int VerySlowMaPeriod
	{
		get => _verySlowMaPeriod.Value;
		set => _verySlowMaPeriod.Value = value;
	}

	public bool UseStochastic
	{
		get => _useStochastic.Value;
		set => _useStochastic.Value = value;
	}

	public int StochasticKPeriod
	{
		get => _stochasticKPeriod.Value;
		set => _stochasticKPeriod.Value = value;
	}

	public int StochasticDPeriod
	{
		get => _stochasticDPeriod.Value;
		set => _stochasticDPeriod.Value = value;
	}

	public int StochasticSmooth
	{
		get => _stochasticSmooth.Value;
		set => _stochasticSmooth.Value = value;
	}

	public decimal StochasticUpperLevel
	{
		get => _stochasticUpperLevel.Value;
		set => _stochasticUpperLevel.Value = value;
	}

	public decimal StochasticLowerLevel
	{
		get => _stochasticLowerLevel.Value;
		set => _stochasticLowerLevel.Value = value;
	}

	public bool UseRsi
	{
		get => _useRsi.Value;
		set => _useRsi.Value = value;
	}

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

	public decimal RsiUpperLevel
	{
		get => _rsiUpperLevel.Value;
		set => _rsiUpperLevel.Value = value;
	}

	public decimal RsiLowerLevel
	{
		get => _rsiLowerLevel.Value;
		set => _rsiLowerLevel.Value = value;
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();

		_pipSize = 0m;
		_fastMa = null;
		_slowMa = null;
		_verySlowMa = null;
		_stochastic = null;
		_rsi = null;
		ResetTradeState();
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		// Prepare pip size once the security is available.
		_pipSize = CalculatePipSize();
		// Clear any leftover state from previous runs.
		ResetTradeState();

		// Instantiate indicators with the configured lengths.
		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
		_verySlowMa = new ExponentialMovingAverage { Length = VerySlowMaPeriod };

		if (UseStochastic)
		{
			_stochastic = new StochasticOscillator();
			_stochastic.K.Length = StochasticKPeriod;
			_stochastic.D.Length = StochasticDPeriod;
		}

		if (UseRsi)
		{
			_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		}

		// Subscribe to candle updates and bind the three EMAs.
		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_fastMa, _slowMa, _verySlowMa, ProcessCandle)
		.Start();

		// Draw indicators and trades when a chart area is available.
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _slowMa);
			DrawIndicator(area, _verySlowMa);
			if (_stochastic != null)
				DrawIndicator(area, _stochastic);
			if (_rsi != null)
				DrawIndicator(area, _rsi);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastMaValue, decimal slowMaValue, decimal verySlowMaValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Process stochastic and RSI manually only when enabled.
		IIndicatorValue stochasticValue = null;
		if (UseStochastic && _stochastic != null)
			stochasticValue = _stochastic.Process(candle);

		decimal rsiValue = 50m;
		if (UseRsi && _rsi != null)
		{
			var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.CloseTime) { IsFinal = true });
			rsiValue = rsiResult.IsFormed ? rsiResult.ToDecimal() : 50m;
		}

		if (UseMovingAverageFilter && (!_fastMa.IsFormed || !_slowMa.IsFormed || !_verySlowMa.IsFormed))
		return;

		if (UseStochastic && !_stochastic.IsFormed)
		return;

		if (UseRsi && !_rsi.IsFormed)
		return;

		// Manage the active position before evaluating new signals.
		UpdateRiskManagement(candle);

		// Aggregate votes from all enabled filters.
		var filters = 0;
		var upVotes = 0;
		var downVotes = 0;

		if (UseMovingAverageFilter)
		{
			filters++;

			if (fastMaValue > slowMaValue && slowMaValue > verySlowMaValue)
			upVotes++;
		else if (fastMaValue < slowMaValue && slowMaValue < verySlowMaValue)
			downVotes++;
		}

		if (UseStochastic && stochasticValue != null)
		{
			if (stochasticValue is not IStochasticOscillatorValue stoch)
			return;
			if (stoch.K is not decimal stochK || stoch.D is not decimal stochD)
			return;

			filters++;

			if (stochD < stochK && StochasticLowerLevel > stochK)
			upVotes++;
			if (stochD > stochK && StochasticUpperLevel < stochK)
			downVotes++;
		}

		if (UseRsi)
		{
			filters++;

			if (rsiValue < RsiLowerLevel)
			upVotes++;
			if (rsiValue > RsiUpperLevel)
			downVotes++;
		}

		if (filters == 0)
		return;

		var longSignal = AllowLongs && upVotes == filters;
		var shortSignal = AllowShorts && downVotes == filters;

		// Only open a new trade when there is no active position.
		if (Position == 0)
		{
			if (longSignal)
			TryEnterLong(candle);
			else if (shortSignal)
			TryEnterShort(candle);
		}
	}

	private void TryEnterLong(ICandleMessage candle)
	{
		var volume = CalculateEntryVolume();
		if (volume <= 0m)
		return;

		// Enter a long position at market price.
		BuyMarket(volume);

		// Store trade prices for later risk management.
		_entryPrice = candle.ClosePrice;
		_breakevenActivated = false;
		_trailingStop = null;

		var stopOffset = StopLossPips > 0m ? StopLossPips * _pipSize : 0m;
		var takeOffset = TakeProfitPips > 0m ? TakeProfitPips * _pipSize : 0m;

		_stopPrice = stopOffset > 0m ? _entryPrice - stopOffset : null;
		_takePrice = takeOffset > 0m ? _entryPrice + takeOffset : null;
	}

	private void TryEnterShort(ICandleMessage candle)
	{
		var volume = CalculateEntryVolume();
		if (volume <= 0m)
		return;

		// Enter a short position at market price.
		SellMarket(volume);

		// Store trade prices for later risk management.
		_entryPrice = candle.ClosePrice;
		_breakevenActivated = false;
		_trailingStop = null;

		var stopOffset = StopLossPips > 0m ? StopLossPips * _pipSize : 0m;
		var takeOffset = TakeProfitPips > 0m ? TakeProfitPips * _pipSize : 0m;

		_stopPrice = stopOffset > 0m ? _entryPrice + stopOffset : null;
		_takePrice = takeOffset > 0m ? _entryPrice - takeOffset : null;
	}

	private void UpdateRiskManagement(ICandleMessage candle)
	{
		// Manage long positions first.
		if (Position > 0)
		{
			HandleBreakevenLong(candle);
			HandleTrailingLong(candle);

			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket(Math.Abs(Position));
				ResetTradeState();
			}
			else if (_takePrice is decimal take && candle.HighPrice >= take)
			{
				SellMarket(Math.Abs(Position));
				ResetTradeState();
			}
		}
		// Manage short positions in the same fashion.
		else if (Position < 0)
		{
			HandleBreakevenShort(candle);
			HandleTrailingShort(candle);

			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket(Math.Abs(Position));
				ResetTradeState();
			}
			else if (_takePrice is decimal take && candle.LowPrice <= take)
			{
				BuyMarket(Math.Abs(Position));
				ResetTradeState();
			}
		}
		// Reset helper state when flat.
		else
		{
			ResetTradeState();
		}
	}

	// Move the stop to breakeven for long trades once profit reaches the threshold.
	private void HandleBreakevenLong(ICandleMessage candle)
	{
		if (_breakevenActivated || BreakevenPips <= 0m)
		return;

		var trigger = _entryPrice + BreakevenPips * _pipSize;
		if (candle.HighPrice >= trigger)
		{
			_breakevenActivated = true;
			UpdateLongStop(_entryPrice);
		}
	}

	// Move the stop to breakeven for short trades once profit reaches the threshold.
	private void HandleBreakevenShort(ICandleMessage candle)
	{
		if (_breakevenActivated || BreakevenPips <= 0m)
		return;

		var trigger = _entryPrice - BreakevenPips * _pipSize;
		if (candle.LowPrice <= trigger)
		{
			_breakevenActivated = true;
			UpdateShortStop(_entryPrice);
		}
	}

	// Update trailing logic for long trades using distance and step thresholds.
	private void HandleTrailingLong(ICandleMessage candle)
	{
		if (TrailingStopPips <= 0m)
		return;

		var distance = TrailingStopPips * _pipSize;
		if (distance <= 0m)
		return;

		var step = TrailingStepPips * _pipSize;
		var desiredStop = candle.ClosePrice - distance;

		if (_trailingStop is null)
		{
			var activationPrice = _entryPrice + distance + step;
			if (candle.HighPrice >= activationPrice)
			{
				_trailingStop = desiredStop;
				UpdateLongStop(desiredStop);
			}
		}
		else if (desiredStop > _trailingStop.Value + step)
		{
			_trailingStop = desiredStop;
			UpdateLongStop(desiredStop);
		}
	}

	// Update trailing logic for short trades using distance and step thresholds.
	private void HandleTrailingShort(ICandleMessage candle)
	{
		if (TrailingStopPips <= 0m)
		return;

		var distance = TrailingStopPips * _pipSize;
		if (distance <= 0m)
		return;

		var step = TrailingStepPips * _pipSize;
		var desiredStop = candle.ClosePrice + distance;

		if (_trailingStop is null)
		{
			var activationPrice = _entryPrice - distance - step;
			if (candle.LowPrice <= activationPrice)
			{
				_trailingStop = desiredStop;
				UpdateShortStop(desiredStop);
			}
		}
		else if (desiredStop < _trailingStop.Value - step)
		{
			_trailingStop = desiredStop;
			UpdateShortStop(desiredStop);
		}
	}

	// Ensure the long stop can only move upward.
	private void UpdateLongStop(decimal newLevel)
	{
		if (_stopPrice is null || newLevel > _stopPrice.Value)
		_stopPrice = newLevel;
	}

	// Ensure the short stop can only move downward.
	private void UpdateShortStop(decimal newLevel)
	{
		if (_stopPrice is null || newLevel < _stopPrice.Value)
		_stopPrice = newLevel;
	}

	// Determine trade size using either fixed volume or risk-based sizing.
	private decimal CalculateEntryVolume()
	{
		if (FixedVolume > 0m)
		return AdjustVolume(FixedVolume);

		var stopOffset = StopLossPips > 0m ? StopLossPips * _pipSize : 0m;
		if (stopOffset <= 0m)
		return AdjustVolume(Volume);

		var riskVolume = GetRiskVolume(stopOffset);
		return AdjustVolume(riskVolume);
	}

	// Translate the configured risk percentage into lots based on stop distance.
	private decimal GetRiskVolume(decimal stopOffset)
	{
		if (stopOffset <= 0m)
		return 0m;

		var priceStep = Security?.PriceStep ?? 0m;
		var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;

		if (priceStep <= 0m || stepPrice <= 0m)
		return 0m;

		var lossPerUnit = stopOffset / priceStep * stepPrice;
		if (lossPerUnit <= 0m)
		return 0m;

		var equity = Portfolio?.CurrentValue ?? 0m;
		if (equity <= 0m)
		return 0m;

		var riskAmount = equity * RiskPercent / 100m;
		if (riskAmount <= 0m)
		return 0m;

		return riskAmount / lossPerUnit;
	}

	// Normalize the requested volume to instrument constraints.
	private decimal AdjustVolume(decimal volume)
	{
		if (volume <= 0m)
		return 0m;

		var security = Security;
		if (security == null)
		return volume;

		var step = security.VolumeStep ?? 0m;

		if (step > 0m)
		{
			var steps = Math.Floor(volume / step);
			var adjusted = steps * step;
			if (adjusted <= 0m)
				adjusted = step;
			return adjusted;
		}

		return volume > 0m ? volume : 0m;
	}

	// Mimic the MetaTrader pip conversion used in the original script.
	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
		return 1m;

		// For crypto and large-price instruments, scale pip size
		// so that pip-based parameters produce meaningful price offsets.
		return step;
	}

	// Clear cached state values when no position is active.
	private void ResetTradeState()
	{
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_trailingStop = null;
		_breakevenActivated = false;
	}
}