GitHub で見る

Kijun-Sen Robotストラテジー

概要

Kijun-Sen Robotストラテジーは、MetaTrader 5のエキスパートアドバイザー「Kijun Sen Robot」をStockSharpの高レベル戦略APIに直接変換したものです。デフォルトで30分足ローソク足で動作し、20期間の線形加重移動平均(LWMA)によって確認されたIchimokuのKijun-senによる価格クロスに焦点を当てています。この戦略は、最も活発な時間帯にのみ取引を行い、ダイナミックなストップ、ブレークイーブン、トレーリングロジックによるポジション保護を強制するという元のエキスパートの考えを維持しています。

インジケーターとデータ

  • Ichimoku:Tenkan、Kijun、Senkou Span Bを6/12/24期間に設定。
  • 線形加重移動平均(LWMA) 20バー:傾き確認と距離フィルタリング用。
  • 30分足ローソク足(デフォルト):シグナル生成用。CandleTypeパラメーターで他の時間軸を選択可能。

トレードロジック

ロングエントリー

  1. ローソク足がKijunラインを下から突き抜ける。ローソク足はラインの下で開いてその上で閉じるか、または前の終値もラインの下にある状態でバー中にラインに触れる必要があります。
  2. 現在のKijunが2バー前と比べて横ばいまたは上昇している。
  3. LWMAがKijunレベルの少なくともMaFilterPips(価格単位に変換)下にあり、基準線を移動平均の上に保つ。
  4. LWMAの傾きが正(現在のLWMAが前の値より大きい)。
  5. 取引時間が[TradingStartHour, TradingEndHour)内、デフォルトは取引所時刻07:00–19:00。

すべての条件が満たされ、戦略がすでにネットロングでない場合、成行買い注文が送信されます(既存のショートがある場合はまず決済)。エントリー価格はローソク足の終値です。

ショートエントリー

  1. ローソク足がKijunラインを上から突き抜ける(ロングロジックの鏡)。
  2. Kijunが2バー前と比べて横ばいまたは下落している。
  3. LWMAがKijunレベルの少なくともMaFilterPips上にある。
  4. LWMAの傾きが負(現在のLWMAが前の値より小さい)。
  5. エントリーは許可された取引ウィンドウ内でのみ発生。

成行売り注文が置かれます(既存のロングエクスポージャーはショートを開く前に閉じられます)。

ポジション管理と決済

  • 初期ストップロス – エントリー価格からStopLossPips下/上に置かれます(インストゥルメントの価格ステップを使って価格単位に変換)。これはMQLバージョンの保護ストップを再現します。
  • ブレークイーブン移動 – 未実現利益がBreakEvenPipsを超えたら、ストップをエントリー価格プラス1pip(ロング)またはマイナス1pip(ショート)に移動します。閾値は同じpip変換ロジックで測定されます。
  • トレーリングストップ – 価格がTrailingStopPips進んだ後、ストップはその距離で価格に追従し、有利な方向にのみ動きます。
  • 固定テイクプロフィットTakeProfitPipsで定義されるオプションの目標。無効化するにはゼロに設定。
  • Kijun傾きによる決済 – ストップがブレークイーブンを超えて移動する前にLWMAがトレードに逆らって転換した場合、ポジションは即座に閉じられ、元のエキスパートの緊急脱出と一致します。
  • 時間フィルター – 新規トレードは設定されたウィンドウ外では無視されますが、オープンなトレードは上記のルールで閉じられるまで引き続き管理されます。
  • 注文処理 – StockSharp戦略は成行注文のみを使用。ティックデータの代わりにローソク足データが使用されるため、元のEAの複雑なリミット対成行エントリーロジックは簡素化されています。

同じバー内でストップロスとテイクプロフィットの両方のレベルが違反される場合、バー内情報なしで保守的を維持するためにストップロスが優先されます。

パラメーター

パラメーター デフォルト値 説明
TenkanPeriod 6 Ichimoku Tenkan-senの長さ。
KijunPeriod 12 Ichimoku Kijun-senの長さ。
SenkouSpanBPeriod 24 Ichimoku Senkou Span Bの長さ。
LwmaPeriod 20 LWMAの確認フィルターの長さ。
MaFilterPips 6 pipでの最小LWMA対Kijun距離。
StopLossPips 50 初期保護ストップ距離。
BreakEvenPips 9 ストップをブレークイーブンに移動するために必要な利益。
TrailingStopPips 10 トレーリングストップ移動の距離。
TakeProfitPips 120 オプションの固定テイクプロフィット距離。
TradingStartHour 7 最初の許可取引時間(含む)。
TradingEndHour 19 最後の許可取引時間(除く)。
CandleType 30分の時間軸 シグナル評価に使用するデータタイプ。

すべてのpipベースのパラメーターはインストゥルメントのPriceStepを使って価格単位に変換されます。3桁または5桁の小数を持つインストゥルメントは、古典的なFX pipサイズを再現するために自動的に10の係数を受け取ります。

実装メモ

  • 変換は_pendingLongLevel_pendingShortLevelを通じてストラテジーのステートフル変数(longcrossshortcrossの動作)を維持し、新規ポジションが新鮮なKijunクロスを必要とすることを保証します。
  • MT5バージョンの「最後のbid/ask」のようなバー内チェックは、ローソク足レベルの条件(OpenCloseHighLow)で近似されます。これにより、StockSharpのバックテストでのロジックが決定論的になります。
  • ポジション保護は、MT5注文変更の代わりにClosePosition()と手動ストップ追跡を使用します。ブレークイーブンとトレーリングの調整は完了したローソク足ごとに一度実行されます。
  • ヘルパーメソッドConvertPipsは、Security.PriceStepまたはSecurity.MinPriceStepを使用してpipから価格への変換を行い、MT5のdigits_adjustルールをエミュレートするために3桁または5桁のティックサイズに10×乗数を適用します。
  • 戦略は高レベルAPIに結びついているため、インジケーターはSubscribeCandles().BindEx(...)を介してバインドされ、チャートの描画(ローソク足、Ichimoku、LWMA、自己トレード)が自動的に設定されます。

使用ガイドライン

  1. 30分足ローソク足をサポートするインストゥルメントに戦略を適用します(または別のCandleTypeを設定)。
  2. 開始前に戦略インスタンスのVolumeを希望の注文サイズに設定します。
  3. オプションで、インストゥルメントのボラティリティを反映するか、特定の通貨ペアの最適化された設定を再現するためにpipベースのパラメーターを調整します。
  4. 高レベルバックテスターまたはライブ環境で実行。戦略は元のエキスパートと同じ取引ウィンドウ、ストップ、トレーリングルールを適用します。
  5. ブレークイーブンとトレーリングの更新を確認するためにログまたはチャートを監視します。コード内のすべてのコメントは、要求に応じて明確さのために英語で記述されています。

Pythonバージョンは意図的に省略されています。このフォルダーにはC#実装のみが提供されています。

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>
/// Ichimoku Kijun-sen robot converted from MetaTrader.
/// The strategy looks for price crossing the Kijun line while a 20-period LWMA confirms the trend.
/// It manages risk with time filters, configurable stop-loss, break-even and trailing rules.
/// </summary>
public class KijunSenRobotStrategy : Strategy
{
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanBPeriod;
	private readonly StrategyParam<int> _lwmaPeriod;
	private readonly StrategyParam<decimal> _maFilterPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _breakEvenPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<int> _tradingStartHour;
	private readonly StrategyParam<int> _tradingEndHour;
	private readonly StrategyParam<DataType> _candleType;

	private Ichimoku _ichimoku = null!;
	private WeightedMovingAverage _lwma = null!;

	private decimal? _previousClose;
	private decimal? _previousMa;
	private decimal? _previousPrevMa;
	private decimal? _previousKijun;
	private decimal? _previousPrevKijun;
	private decimal? _pendingLongLevel;
	private decimal? _pendingShortLevel;

	private bool? _isLongPosition;
	private decimal? _entryPrice;
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private decimal _stopLossDistance;
	private decimal _takeProfitDistance;
	private decimal _breakEvenDistance;
	private decimal _breakEvenStep;
	private decimal _trailingDistance;
	private bool _breakEvenApplied;

	/// <summary>
	/// Tenkan-sen calculation period.
	/// </summary>
	public int TenkanPeriod
	{
		get => _tenkanPeriod.Value;
		set => _tenkanPeriod.Value = value;
	}

	/// <summary>
	/// Kijun-sen calculation period.
	/// </summary>
	public int KijunPeriod
	{
		get => _kijunPeriod.Value;
		set => _kijunPeriod.Value = value;
	}

	/// <summary>
	/// Senkou Span B calculation period.
	/// </summary>
	public int SenkouSpanBPeriod
	{
		get => _senkouSpanBPeriod.Value;
		set => _senkouSpanBPeriod.Value = value;
	}

	/// <summary>
	/// Weighted moving average period used for slope confirmation.
	/// </summary>
	public int LwmaPeriod
	{
		get => _lwmaPeriod.Value;
		set => _lwmaPeriod.Value = value;
	}

	/// <summary>
	/// Minimum distance in pips between price and Kijun required by the LWMA filter.
	/// </summary>
	public decimal MaFilterPips
	{
		get => _maFilterPips.Value;
		set => _maFilterPips.Value = value;
	}

	/// <summary>
	/// Initial stop-loss in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Profit distance in pips required to move the stop-loss to break-even.
	/// </summary>
	public decimal BreakEvenPips
	{
		get => _breakEvenPips.Value;
		set => _breakEvenPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// First trading hour (inclusive) in exchange time.
	/// </summary>
	public int TradingStartHour
	{
		get => _tradingStartHour.Value;
		set => _tradingStartHour.Value = value;
	}

	/// <summary>
	/// Last trading hour (exclusive) in exchange time.
	/// </summary>
	public int TradingEndHour
	{
		get => _tradingEndHour.Value;
		set => _tradingEndHour.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="KijunSenRobotStrategy"/>.
	/// </summary>
	public KijunSenRobotStrategy()
	{
		_tenkanPeriod = Param(nameof(TenkanPeriod), 6)
		.SetGreaterThanZero()
		.SetDisplay("Tenkan Period", "Period for Ichimoku Tenkan line", "Ichimoku")
		
		.SetOptimize(4, 12, 1);

		_kijunPeriod = Param(nameof(KijunPeriod), 12)
		.SetGreaterThanZero()
		.SetDisplay("Kijun Period", "Period for Ichimoku Kijun line", "Ichimoku")
		
		.SetOptimize(8, 20, 1);

		_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 24)
		.SetGreaterThanZero()
		.SetDisplay("Senkou Span B Period", "Period for Ichimoku Senkou Span B", "Ichimoku")
		
		.SetOptimize(18, 30, 1);

		_lwmaPeriod = Param(nameof(LwmaPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("LWMA Period", "Length of the confirmation LWMA", "Trend Filter")
		
		.SetOptimize(10, 40, 2);

		_maFilterPips = Param(nameof(MaFilterPips), 20m)
		.SetNotNegative()
		.SetDisplay("LWMA Filter (pips)", "Minimum distance between price and Kijun required by the LWMA", "Trend Filter")
		
		.SetOptimize(0m, 20m, 1m);

		_stopLossPips = Param(nameof(StopLossPips), 50m)
		.SetNotNegative()
		.SetDisplay("Stop Loss (pips)", "Initial protective stop distance", "Risk Management")
		
		.SetOptimize(20m, 100m, 5m);

		_breakEvenPips = Param(nameof(BreakEvenPips), 9m)
		.SetNotNegative()
		.SetDisplay("Break-even Trigger (pips)", "Profit distance required to protect the position", "Risk Management")
		
		.SetOptimize(5m, 20m, 1m);

		_trailingStopPips = Param(nameof(TrailingStopPips), 10m)
		.SetNotNegative()
		.SetDisplay("Trailing Stop (pips)", "Distance for the trailing stop after the position moves in profit", "Risk Management")
		
		.SetOptimize(5m, 30m, 1m);

		_takeProfitPips = Param(nameof(TakeProfitPips), 120m)
		.SetNotNegative()
		.SetDisplay("Take Profit (pips)", "Optional fixed profit target", "Risk Management")
		
		.SetOptimize(40m, 200m, 10m);

		_tradingStartHour = Param(nameof(TradingStartHour), 7)
		.SetRange(0, 23)
		.SetDisplay("Start Hour", "First trading hour (inclusive)", "Scheduling");

		_tradingEndHour = Param(nameof(TradingEndHour), 19)
		.SetRange(1, 24)
		.SetDisplay("End Hour", "Last trading hour (exclusive)", "Scheduling");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for signal generation", "General");
	}

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

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

		_ichimoku = null!;
		_lwma = null!;

		_previousClose = null;
		_previousMa = null;
		_previousPrevMa = null;
		_previousKijun = null;
		_previousPrevKijun = null;
		_pendingLongLevel = null;
		_pendingShortLevel = null;

		ResetPositionState();
	}

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

		_ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouSpanBPeriod }
		};

		_lwma = new WeightedMovingAverage
		{
			Length = LwmaPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_ichimoku, _lwma, ProcessCandle)
		.Start();

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

		// protection handled manually via SL/TP/trailing
	}

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

		if (!maValue.IsFinal)
		return;

		var ichimokuTyped = (IchimokuValue)ichimokuValue;
		if (ichimokuTyped.Kijun is not decimal kijun)
		return;

		var maCurrent = maValue.ToDecimal();

		ManageOpenPosition(candle, maCurrent);

		if (IsFormedAndOnlineAndAllowTrading() && IsWithinTradingHours(candle.OpenTime))
		{
			EvaluateEntrySignals(candle, kijun, maCurrent);
		}

		UpdateHistory(candle, kijun, maCurrent);
	}

	private void ManageOpenPosition(ICandleMessage candle, decimal maCurrent)
	{
		if (Position == 0)
		{
			if (_isLongPosition != null || _entryPrice != null)
			ResetPositionState();
			return;
		}

		var isLong = Position > 0;
		var actualEntry = _entryPrice ?? candle.ClosePrice;
		if (_isLongPosition is null || _isLongPosition.Value != isLong || _entryPrice is null)
		{
			var entry = actualEntry != 0m ? actualEntry : candle.ClosePrice;
			SetupPositionState(isLong, entry);
		}
		else if (actualEntry != 0m && _entryPrice.Value != actualEntry)
		{
			_entryPrice = actualEntry;
			if (_isLongPosition.Value)
			{
				_stopLossPrice = _stopLossDistance > 0m ? _entryPrice - _stopLossDistance : null;
				_takeProfitPrice = _takeProfitDistance > 0m ? _entryPrice + _takeProfitDistance : null;
			}
			else
			{
				_stopLossPrice = _stopLossDistance > 0m ? _entryPrice + _stopLossDistance : null;
				_takeProfitPrice = _takeProfitDistance > 0m ? _entryPrice - _takeProfitDistance : null;
			}
		}

		if (_entryPrice is not decimal entryPrice)
		return;

		_isLongPosition = isLong;

		if (_previousMa.HasValue && _previousPrevMa.HasValue && _stopLossPrice.HasValue)
		{
			if (isLong && _stopLossPrice.Value < entryPrice && _previousMa.Value < _previousPrevMa.Value)
			{
				ClosePositionAndReset();
				return;
			}

			if (!isLong && _stopLossPrice.Value > entryPrice && _previousMa.Value > _previousPrevMa.Value)
			{
				ClosePositionAndReset();
				return;
			}
		}

		if (isLong)
		{
			ApplyBreakEvenAndTrailingForLong(candle, entryPrice);

			if (CheckStopLossHit(candle.LowPrice, _stopLossPrice))
			{
				ClosePositionAndReset();
				return;
			}

			if (CheckTakeProfitHit(candle.HighPrice, _takeProfitPrice))
			{
				ClosePositionAndReset();
				return;
			}
		}
		else
		{
			ApplyBreakEvenAndTrailingForShort(candle, entryPrice);

			if (CheckStopLossHitForShort(candle.HighPrice, _stopLossPrice))
			{
				ClosePositionAndReset();
				return;
			}

			if (CheckTakeProfitHitForShort(candle.LowPrice, _takeProfitPrice))
			{
				ClosePositionAndReset();
				return;
			}
		}
	}

	private void ApplyBreakEvenAndTrailingForLong(ICandleMessage candle, decimal entryPrice)
	{
		if (!_breakEvenApplied && _breakEvenDistance > 0m)
		{
			if (candle.ClosePrice - entryPrice >= _breakEvenDistance)
			{
				var newStop = entryPrice + (_breakEvenStep > 0m ? _breakEvenStep : 0m);
				if (_stopLossPrice is not decimal currentStop || newStop > currentStop)
				_stopLossPrice = newStop;
				_breakEvenApplied = true;
			}
		}

		if (_trailingDistance > 0m && candle.ClosePrice - entryPrice >= _trailingDistance)
		{
			var newStop = candle.ClosePrice - _trailingDistance;
			if (_stopLossPrice is not decimal currentStop || newStop > currentStop)
			_stopLossPrice = newStop;
		}
	}

	private void ApplyBreakEvenAndTrailingForShort(ICandleMessage candle, decimal entryPrice)
	{
		if (!_breakEvenApplied && _breakEvenDistance > 0m)
		{
			if (entryPrice - candle.ClosePrice >= _breakEvenDistance)
			{
				var newStop = entryPrice - (_breakEvenStep > 0m ? _breakEvenStep : 0m);
				if (_stopLossPrice is not decimal currentStop || newStop < currentStop)
				_stopLossPrice = newStop;
				_breakEvenApplied = true;
			}
		}

		if (_trailingDistance > 0m && entryPrice - candle.ClosePrice >= _trailingDistance)
		{
			var newStop = candle.ClosePrice + _trailingDistance;
			if (_stopLossPrice is not decimal currentStop || newStop < currentStop)
			_stopLossPrice = newStop;
		}
	}

	private static bool CheckStopLossHit(decimal lowPrice, decimal? stopPrice)
	{
		return stopPrice.HasValue && lowPrice <= stopPrice.Value;
	}

	private static bool CheckTakeProfitHit(decimal highPrice, decimal? takeProfitPrice)
	{
		return takeProfitPrice.HasValue && highPrice >= takeProfitPrice.Value;
	}

	private static bool CheckStopLossHitForShort(decimal highPrice, decimal? stopPrice)
	{
		return stopPrice.HasValue && highPrice >= stopPrice.Value;
	}

	private static bool CheckTakeProfitHitForShort(decimal lowPrice, decimal? takeProfitPrice)
	{
		return takeProfitPrice.HasValue && lowPrice <= takeProfitPrice.Value;
	}

	private void EvaluateEntrySignals(ICandleMessage candle, decimal kijun, decimal maCurrent)
	{
		if (_previousClose is not decimal previousClose ||
		_previousMa is not decimal previousMa ||
		_previousKijun is not decimal previousKijun)
		{
			return;
		}

		var maTrendUp = maCurrent > previousMa;
		var maTrendDown = maCurrent < previousMa;
		var filterOffset = ConvertPips(MaFilterPips);

		var priceOpenedBelow = candle.OpenPrice < kijun;
		var priceOpenedAbove = candle.OpenPrice > kijun;
		var priceClosedAbove = candle.ClosePrice > kijun;
		var priceClosedBelow = candle.ClosePrice < kijun;
		var priceTouchedBelow = candle.LowPrice <= kijun;
		var priceTouchedAbove = candle.HighPrice >= kijun;
		var priceWasBelow = previousClose < previousKijun;
		var priceWasAbove = previousClose > previousKijun;
		var kijunNotFalling = !_previousPrevKijun.HasValue || kijun >= _previousPrevKijun.Value;
		var kijunNotRising = !_previousPrevKijun.HasValue || kijun <= _previousPrevKijun.Value;

		if (_pendingLongLevel is null)
		{
			if (priceClosedAbove && (priceOpenedBelow || priceWasBelow || priceTouchedBelow) && kijunNotFalling)
			{
				if (filterOffset <= 0m || maCurrent < kijun - filterOffset)
				{
					_pendingLongLevel = kijun;
					_pendingShortLevel = null;
				}
			}
		}

		if (_pendingShortLevel is null)
		{
			if (priceClosedBelow && (priceOpenedAbove || priceWasAbove || priceTouchedAbove) && kijunNotRising)
			{
				if (filterOffset <= 0m || maCurrent > kijun + filterOffset)
				{
					_pendingShortLevel = kijun;
					_pendingLongLevel = null;
				}
			}
		}

		var volume = Volume + Math.Abs(Position);
		if (volume <= 0m)
		return;

		if (_pendingLongLevel.HasValue && maTrendUp && Position <= 0)
		{
			BuyMarket(volume);
			SetupPositionState(true, candle.ClosePrice);
			_pendingLongLevel = null;
			_pendingShortLevel = null;
			return;
		}

		if (_pendingShortLevel.HasValue && maTrendDown && Position >= 0)
		{
			SellMarket(volume);
			SetupPositionState(false, candle.ClosePrice);
			_pendingLongLevel = null;
			_pendingShortLevel = null;
		}
	}

	private void UpdateHistory(ICandleMessage candle, decimal kijun, decimal maCurrent)
	{
		_previousPrevKijun = _previousKijun;
		_previousKijun = kijun;

		_previousPrevMa = _previousMa;
		_previousMa = maCurrent;

		_previousClose = candle.ClosePrice;
	}

	private bool IsWithinTradingHours(DateTimeOffset time)
	{
		var hour = time.Hour;
		return hour >= TradingStartHour && hour < TradingEndHour;
	}

	private void SetupPositionState(bool isLong, decimal entryPrice)
	{
		_isLongPosition = isLong;
		_entryPrice = entryPrice;
		_breakEvenApplied = false;

		_stopLossDistance = ConvertPips(StopLossPips);
		_takeProfitDistance = ConvertPips(TakeProfitPips);
		_breakEvenDistance = ConvertPips(BreakEvenPips);
		_breakEvenStep = ConvertPips(1m);
		_trailingDistance = ConvertPips(TrailingStopPips);

		_stopLossPrice = _stopLossDistance > 0m ? (isLong ? entryPrice - _stopLossDistance : entryPrice + _stopLossDistance) : null;
		_takeProfitPrice = _takeProfitDistance > 0m ? (isLong ? entryPrice + _takeProfitDistance : entryPrice - _takeProfitDistance) : null;
	}

	private void ClosePositionAndReset()
	{
		if (Position != 0)
			if (Position > 0) SellMarket(Math.Abs(Position)); else if (Position < 0) BuyMarket(Math.Abs(Position));

		ResetPositionState();
	}

	private void ResetPositionState()
	{
		_isLongPosition = null;
		_entryPrice = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_stopLossDistance = 0m;
		_takeProfitDistance = 0m;
		_breakEvenDistance = 0m;
		_breakEvenStep = 0m;
		_trailingDistance = 0m;
		_breakEvenApplied = false;
	}

	private decimal ConvertPips(decimal value)
	{
		if (value <= 0m)
		return 0m;

		var step = GetPipStep();
		return value * step;
	}

	private decimal GetPipStep()
	{
		var priceStep = Security?.PriceStep;
		if (priceStep is null || priceStep <= 0m)
		return 1m;

		var stepValue = priceStep.Value;
		var decimals = GetDecimalPlaces(stepValue);
		if (decimals == 3 || decimals == 5)
		return stepValue * 10m;

		return stepValue;
	}

	private static int GetDecimalPlaces(decimal value)
	{
		value = Math.Abs(value);
		var decimals = 0;
		while (value != Math.Truncate(value) && decimals < 10)
		{
			value *= 10m;
			decimals++;
		}

		return decimals;
	}
}