GitHub で見る

トリプルMAチャネルクロスオーバー戦略

概要

トリプルMAチャネルクロスオーバー戦略は、高速移動平均が中速と低速の移動平均を両方とも突き抜けるときに方向性のある ブレイクアウトを取引します。ドンチャン形式の価格チャネルを使用して出口を管理し、オプションの自動ストップロスと テイクプロフィットレベルを提供します。この変換はオリジナルのMetaTrader「3MACross EA」に基づいており、その設定可能な 移動平均構造、リスク管理、およびトレーリングロジックを維持しています。

戦略は設定可能な数のポジションまでスケールし、手動のpipベースのリスクターゲットをサポートし、適応的な出口のために チャネルに従うことができます。有効化されると、ブレイクイーブントリガーはストップロスをエントリー価格プラス安全 バッファに押し上げます。

取引ロジック

  • エントリー条件
    • ロング: 高速移動平均が中速と低速の両方の平均を上回るように交差する。Trade On Closeが有効な場合、交差は完全に 閉じたローソク足で発生する必要があります。無効な場合、高速平均が両方の低速平均を上回っている間はロングシグナルが 許可されます。
    • ショート: 高速移動平均が同じ確認ロジックで中速と低速の平均を下回るように交差する。
    • 反対側の既存ポジションは即座にクローズされ反転されます。同じ方向へのスケーリングはMax Positionsに達するまで 許可されます。
  • エグジット条件
    • 設定されたテイクプロフィットまたはチャネルベースのターゲットに到達する価格。
    • 動的ストップレベルに触れる価格(手動距離、トレーリングストップ、ブレイクイーブン移動、またはチャネルベースのストップ)。
    • オプションのトレーリングストップは、価格が少なくともトレーリングステップ距離だけ有利に動いた後に調整されます。

リスク管理

  • ストップとターゲットは手動でpipsで定義するか、Auto SL/TPが有効な場合は価格チャネルから導出できます。
  • トレーリングストップとブレイクイーブンロジックはオリジナルのエキスパートアドバイザーを反映します。ストップは 有利な方向にのみ動き、決して緩められません。
  • ドンチャンチャネルは自動ストップロスとテイクプロフィット配置に使用できる自然なサポート/レジスタンス境界を提供します。
  • Max Positionsはスケーリングステップの数を制限し、制御されていないピラミッディングを防ぎます。

主要パラメーター

パラメーター 説明
Volume 各スケーリングステップの注文サイズ。
Stop Loss (pips) 保護ストップの固定距離。0に設定して無効にする。
Take Profit (pips) 利益目標の固定距離。0に設定して無効にする。
Trailing Stop (pips) トレーリングストップが使用する距離。0はトレーリングを無効にする。
Trailing Step (pips) トレーリングストップを更新する前に必要な最小前進。
Break Even (pips) ブレイクイーブンストップを固定する前に必要な利益。
Auto SL/TP 固定距離の代わりにドンチャンチャネルをストップロスとテイクプロフィット配置に使用する。
Trade On Close 交差が閉じたローソク足で確認されることを要求する。無効にすると、各バーで平均のアライメントが確認される。
Max Positions 方向ごとの最大スケーリングステップ数。
Fast/Middle/Slow MA Period 移動平均の長さ。
Fast/Middle/Slow MA Shift 各移動平均に適用されるオプションのシフト(バー単位)。
Fast/Middle/Slow MA Type 移動平均計算モード(単純、指数、平滑化、加重)。
Channel Period ドンチャンチャネルの高値/安値のルックバック。
Candle Type 戦略が処理するローソク足の時間軸。

実装ノート

  • pip距離はSecurity.PriceStepを使用して変換されます。有効なティックサイズのないインストゥルメントの場合、戦略は pip当たり1価格単位の距離にフォールバックします。
  • 自動チャネル管理はストップロスとテイクプロフィットレベルを現在価格により近い方向にのみ移動させ続けます;決して 拡大されません。
  • ブレイクイーブン有効化はトレーリングステップを追加バッファとして再利用し、オリジナルEAの動作に合わせます。
  • 戦略はStockSharpの高レベルAPIとの使用のために設計されており、視覚的分析のためのチャートレンダリング(MAとドンチャン チャネル)を処理します。
  • クロスオーバーシグナルが有効であるように、低速移動平均とチャネル期間に対して十分な履歴データの深さを確保してください。

使用方法

  1. 戦略をインストゥルメントに適用し、希望するローソク足の時間軸を設定する。
  2. オリジナルEAまたはお使いの適応に合わせて移動平均の期間/方法を設定する。
  3. 手動pipsベースのリスク設定と自動チャネル出口の有効化のどちらかを選択する。
  4. 戦略を開始する;設定されたローソク足を購読し、インジケーターを計算し、クロスオーバー条件が満たされたときに取引する。
  5. ログとチャートオーバーレイを通じてトレーリングストップとブレイクイーブン調整を監視する。

免責事項: 自動取引には重大なリスクが伴います。ライブ市場に展開する前に、履歴データとシミュレーション環境で 戦略を徹底的にテストしてください。

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>
/// Triple moving average crossover strategy that uses a Donchian style price channel for risk management.
/// </summary>
public class TripleMaChannelCrossoverStrategy : Strategy
{
	/// <summary>
	/// Moving average calculation modes supported by <see cref="TripleMaChannelCrossoverStrategy"/>.
	/// </summary>
	public enum MovingAverageModes
	{
		/// <summary>
		/// Simple moving average.
		/// </summary>
		Simple,

		/// <summary>
		/// Exponential moving average.
		/// </summary>
		Exponential,

		/// <summary>
		/// Smoothed moving average (SMMA).
		/// </summary>
		Smoothed,

		/// <summary>
		/// Linear weighted moving average.
		/// </summary>
		Weighted,
	}
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _trailingStopPips;
	private readonly StrategyParam<int> _trailingStepPips;
	private readonly StrategyParam<int> _breakEvenPips;
	private readonly StrategyParam<bool> _useAutoTargets;
	private readonly StrategyParam<bool> _tradeOnClose;
	private readonly StrategyParam<int> _maxPositionCount;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _fastShift;
	private readonly StrategyParam<MovingAverageModes> _fastMaType;
	private readonly StrategyParam<int> _middlePeriod;
	private readonly StrategyParam<int> _middleShift;
	private readonly StrategyParam<MovingAverageModes> _middleMaType;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _slowShift;
	private readonly StrategyParam<MovingAverageModes> _slowMaType;
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private IIndicator _fastMa = null!;
	private IIndicator _middleMa = null!;
	private IIndicator _slowMa = null!;
	private DonchianChannels _channel = null!;

	private decimal _prevFast;
	private decimal _prevMiddle;
	private decimal _prevSlow;
	private bool _hasPreviousValues;

	private decimal _tickSize;

	private decimal? _longStop;
	private decimal? _longTake;
	private decimal _longEntryPrice;
	private bool _longBreakEvenActivated;

	private decimal? _shortStop;
	private decimal? _shortTake;
	private decimal _shortEntryPrice;
	private bool _shortBreakEvenActivated;


	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

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

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

	/// <summary>
	/// Minimal step for trailing stop adjustments in pips.
	/// </summary>
	public int TrailingStepPips
	{
		get => _trailingStepPips.Value;
		set => _trailingStepPips.Value = value;
	}

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

	/// <summary>
	/// Enable automatic SL/TP placement based on the price channel.
	/// </summary>
	public bool UseAutoTargets
	{
		get => _useAutoTargets.Value;
		set => _useAutoTargets.Value = value;
	}

	/// <summary>
	/// Trade only when the crossover is confirmed on the closed bar.
	/// </summary>
	public bool TradeOnClose
	{
		get => _tradeOnClose.Value;
		set => _tradeOnClose.Value = value;
	}

	/// <summary>
	/// Maximum number of scaled-in positions.
	/// </summary>
	public int MaxPositionCount
	{
		get => _maxPositionCount.Value;
		set => _maxPositionCount.Value = value;
	}

	/// <summary>
	/// Period for the fast moving average.
	/// </summary>
	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	/// <summary>
	/// Shift for the fast moving average.
	/// </summary>
	public int FastShift
	{
		get => _fastShift.Value;
		set => _fastShift.Value = value;
	}

	/// <summary>
	/// Type of the fast moving average.
	/// </summary>
	public MovingAverageModes FastMaType
	{
		get => _fastMaType.Value;
		set => _fastMaType.Value = value;
	}

	/// <summary>
	/// Period for the middle moving average.
	/// </summary>
	public int MiddlePeriod
	{
		get => _middlePeriod.Value;
		set => _middlePeriod.Value = value;
	}

	/// <summary>
	/// Shift for the middle moving average.
	/// </summary>
	public int MiddleShift
	{
		get => _middleShift.Value;
		set => _middleShift.Value = value;
	}

	/// <summary>
	/// Type of the middle moving average.
	/// </summary>
	public MovingAverageModes MiddleMaType
	{
		get => _middleMaType.Value;
		set => _middleMaType.Value = value;
	}

	/// <summary>
	/// Period for the slow moving average.
	/// </summary>
	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	/// <summary>
	/// Shift for the slow moving average.
	/// </summary>
	public int SlowShift
	{
		get => _slowShift.Value;
		set => _slowShift.Value = value;
	}

	/// <summary>
	/// Type of the slow moving average.
	/// </summary>
	public MovingAverageModes SlowMaType
	{
		get => _slowMaType.Value;
		set => _slowMaType.Value = value;
	}

	/// <summary>
	/// Lookback period for the Donchian price channel.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

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

	/// <summary>
	/// Initialize <see cref="TripleMaChannelCrossoverStrategy"/>.
	/// </summary>
	public TripleMaChannelCrossoverStrategy()
	{

		_stopLossPips = Param(nameof(StopLossPips), 0)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 145)
			.SetDisplay("Take Profit (pips)", "Take profit distance", "Risk");

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

		_trailingStepPips = Param(nameof(TrailingStepPips), 5)
			.SetDisplay("Trailing Step (pips)", "Minimal trailing adjustment", "Risk");

		_breakEvenPips = Param(nameof(BreakEvenPips), 15)
			.SetDisplay("Break Even (pips)", "Profit to move stop to break-even", "Risk");

		_useAutoTargets = Param(nameof(UseAutoTargets), false)
			.SetDisplay("Auto SL/TP", "Use channel for stop & take", "Risk");

		_tradeOnClose = Param(nameof(TradeOnClose), false)
			.SetDisplay("Trade On Close", "Confirm cross on closed bar", "Signals");

		_maxPositionCount = Param(nameof(MaxPositionCount), 5)
			.SetGreaterThanZero()
			.SetDisplay("Max Positions", "Maximum scaling steps", "Trading");

		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast MA Period", "First moving average", "Moving Averages");

		_fastShift = Param(nameof(FastShift), 0)
			.SetDisplay("Fast MA Shift", "Bars to shift fast MA", "Moving Averages");

		_fastMaType = Param(nameof(FastMaType), MovingAverageModes.Smoothed)
			.SetDisplay("Fast MA Type", "Method for fast MA", "Moving Averages");

		_middlePeriod = Param(nameof(MiddlePeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Middle MA Period", "Second moving average", "Moving Averages");

		_middleShift = Param(nameof(MiddleShift), 0)
			.SetDisplay("Middle MA Shift", "Bars to shift middle MA", "Moving Averages");

		_middleMaType = Param(nameof(MiddleMaType), MovingAverageModes.Smoothed)
			.SetDisplay("Middle MA Type", "Method for middle MA", "Moving Averages");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Third moving average", "Moving Averages");

		_slowShift = Param(nameof(SlowShift), 0)
			.SetDisplay("Slow MA Shift", "Bars to shift slow MA", "Moving Averages");

		_slowMaType = Param(nameof(SlowMaType), MovingAverageModes.Smoothed)
			.SetDisplay("Slow MA Type", "Method for slow MA", "Moving Averages");

		_channelPeriod = Param(nameof(ChannelPeriod), 15)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Price channel lookback", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0m;
		_prevMiddle = 0m;
		_prevSlow = 0m;
		_hasPreviousValues = false;
		_longStop = null;
		_longTake = null;
		_longEntryPrice = 0m;
		_longBreakEvenActivated = false;
		_shortStop = null;
		_shortTake = null;
		_shortEntryPrice = 0m;
		_shortBreakEvenActivated = false;
		_tickSize = 0m;
	}

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

		_fastMa = CreateMovingAverage(FastMaType, FastPeriod);
		_middleMa = CreateMovingAverage(MiddleMaType, MiddlePeriod);
		_slowMa = CreateMovingAverage(SlowMaType, SlowPeriod);
		_channel = new DonchianChannels { Length = ChannelPeriod };

		_tickSize = Security.PriceStep ?? 1m;
		if (_tickSize <= 0)
			_tickSize = 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_fastMa, _middleMa, _slowMa, _channel, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _middleMa);
			DrawIndicator(area, _slowMa);
			DrawIndicator(area, _channel);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastVal, IIndicatorValue middleVal, IIndicatorValue slowVal, IIndicatorValue channelVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_fastMa.IsFormed || !_middleMa.IsFormed || !_slowMa.IsFormed || !_channel.IsFormed)
			return;

		var fastValue = fastVal.IsEmpty ? 0m : fastVal.GetValue<decimal>();
		var middleValue = middleVal.IsEmpty ? 0m : middleVal.GetValue<decimal>();
		var slowValue = slowVal.IsEmpty ? 0m : slowVal.GetValue<decimal>();

		var channelValue = (DonchianChannelsValue)channelVal;
		var channelUpper = channelValue.UpperBand as decimal?;
		var channelLower = channelValue.LowerBand as decimal?;

		UpdateLongTargets(candle, channelUpper, channelLower);
		UpdateShortTargets(candle, channelUpper, channelLower);
		CheckExits(candle);

		var crossUp = CalculateCrossUp(fastValue, middleValue, slowValue);
		var crossDown = CalculateCrossDown(fastValue, middleValue, slowValue);

		if (crossUp)
		{
			TryEnterLong(candle, channelUpper, channelLower);
		}
		else if (crossDown)
		{
			TryEnterShort(candle, channelUpper, channelLower);
		}

		_prevFast = fastValue;
		_prevMiddle = middleValue;
		_prevSlow = slowValue;
		_hasPreviousValues = true;
	}

	private bool CalculateCrossUp(decimal fastValue, decimal middleValue, decimal slowValue)
	{
		if (TradeOnClose)
		{
			if (!_hasPreviousValues)
				return false;

			var crossMiddle = _prevFast <= _prevMiddle && fastValue > middleValue;
			var crossSlow = _prevFast <= _prevSlow && fastValue > slowValue;
			return crossMiddle && crossSlow;
		}

		return fastValue > middleValue && fastValue > slowValue;
	}

	private bool CalculateCrossDown(decimal fastValue, decimal middleValue, decimal slowValue)
	{
		if (TradeOnClose)
		{
			if (!_hasPreviousValues)
				return false;

			var crossMiddle = _prevFast >= _prevMiddle && fastValue < middleValue;
			var crossSlow = _prevFast >= _prevSlow && fastValue < slowValue;
			return crossMiddle && crossSlow;
		}

		return fastValue < middleValue && fastValue < slowValue;
	}

	private void TryEnterLong(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position >= 0)
		{
			var maxVolume = Volume * MaxPositionCount;
			var currentLong = Position;
			if (currentLong >= maxVolume)
				return;

			var targetVolume = Math.Min(Volume, maxVolume - currentLong);
			if (targetVolume <= 0m)
				return;

			BuyMarket();
		}
		else
		{
			BuyMarket();
			ResetShortState();
		}

		_longEntryPrice = candle.ClosePrice;
		_longBreakEvenActivated = false;
		SetLongTargets(candle, channelUpper, channelLower);
	}

	private void TryEnterShort(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position <= 0)
		{
			var maxVolume = Volume * MaxPositionCount;
			var currentShort = -Position;
			if (currentShort >= maxVolume)
				return;

			var targetVolume = Math.Min(Volume, maxVolume - currentShort);
			if (targetVolume <= 0m)
				return;

			SellMarket();
		}
		else
		{
			SellMarket();
			ResetLongState();
		}

		_shortEntryPrice = candle.ClosePrice;
		_shortBreakEvenActivated = false;
		SetShortTargets(candle, channelUpper, channelLower);
	}

	private void SetLongTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		var entryPrice = candle.ClosePrice;
		var stopDistance = GetDistance(StopLossPips);
		var takeDistance = GetDistance(TakeProfitPips);
		var breakEvenDistance = GetDistance(BreakEvenPips);

		if (UseAutoTargets)
		{
			if (channelLower is decimal lower)
			{
				var candidate = lower;
				if (BreakEvenPips > 0)
					candidate = Math.Max(candidate, entryPrice - breakEvenDistance);
				_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, candidate) : candidate;
			}
			else if (stopDistance > 0m)
			{
				_longStop = entryPrice - stopDistance;
			}

			if (channelUpper is decimal upper)
			{
				var candidate = upper;
				if (BreakEvenPips > 0)
					candidate = Math.Max(candidate, entryPrice + breakEvenDistance);
				_longTake = _longTake.HasValue ? Math.Max(_longTake.Value, candidate) : candidate;
			}
			else if (takeDistance > 0m)
			{
				_longTake = entryPrice + takeDistance;
			}
		}
		else
		{
			_longStop = stopDistance > 0m ? entryPrice - stopDistance : null;
			_longTake = takeDistance > 0m ? entryPrice + takeDistance : null;
		}
	}

	private void SetShortTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		var entryPrice = candle.ClosePrice;
		var stopDistance = GetDistance(StopLossPips);
		var takeDistance = GetDistance(TakeProfitPips);
		var breakEvenDistance = GetDistance(BreakEvenPips);

		if (UseAutoTargets)
		{
			if (channelUpper is decimal upper)
			{
				var candidate = upper;
				if (BreakEvenPips > 0)
					candidate = Math.Min(candidate, entryPrice + breakEvenDistance);
				_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, candidate) : candidate;
			}
			else if (stopDistance > 0m)
			{
				_shortStop = entryPrice + stopDistance;
			}

			if (channelLower is decimal lower)
			{
				var candidate = lower;
				if (BreakEvenPips > 0)
					candidate = Math.Min(candidate, entryPrice - breakEvenDistance);
				_shortTake = _shortTake.HasValue ? Math.Min(_shortTake.Value, candidate) : candidate;
			}
			else if (takeDistance > 0m)
			{
				_shortTake = entryPrice - takeDistance;
			}
		}
		else
		{
			_shortStop = stopDistance > 0m ? entryPrice + stopDistance : null;
			_shortTake = takeDistance > 0m ? entryPrice - takeDistance : null;
		}
	}

	private void UpdateLongTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position <= 0)
		{
			ResetLongState();
			return;
		}

		var breakEvenDistance = GetDistance(BreakEvenPips);
		var trailingDistance = GetDistance(TrailingStopPips);
		var trailingStep = GetDistance(TrailingStepPips);
		var entryPrice = _longEntryPrice;

		if (UseAutoTargets && channelLower is decimal lower)
		{
			var candidate = lower;
			if (BreakEvenPips > 0)
				candidate = Math.Max(candidate, entryPrice - breakEvenDistance);
			_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, candidate) : candidate;
		}

		if (UseAutoTargets && channelUpper is decimal upper)
		{
			var candidate = upper;
			if (BreakEvenPips > 0)
				candidate = Math.Max(candidate, entryPrice + breakEvenDistance);
			_longTake = _longTake.HasValue ? Math.Max(_longTake.Value, candidate) : candidate;
		}

		if (trailingDistance > 0m)
		{
			var candidate = candle.ClosePrice - trailingDistance;
			if (_longStop is decimal currentStop)
			{
				if (candidate - currentStop >= Math.Max(trailingStep, _tickSize))
					_longStop = candidate;
			}
			else
			{
				_longStop = candidate;
			}
		}

		if (BreakEvenPips > 0 && !_longBreakEvenActivated)
		{
			var activationPrice = entryPrice + breakEvenDistance + Math.Max(0m, trailingStep);
			var targetStop = entryPrice + breakEvenDistance;
			if (candle.ClosePrice >= activationPrice)
			{
				_longBreakEvenActivated = true;
				_longStop = _longStop.HasValue ? Math.Max(_longStop.Value, targetStop) : targetStop;
			}
		}
	}

	private void UpdateShortTargets(ICandleMessage candle, decimal? channelUpper, decimal? channelLower)
	{
		if (Position >= 0)
		{
			ResetShortState();
			return;
		}

		var breakEvenDistance = GetDistance(BreakEvenPips);
		var trailingDistance = GetDistance(TrailingStopPips);
		var trailingStep = GetDistance(TrailingStepPips);
		var entryPrice = _shortEntryPrice;

		if (UseAutoTargets && channelUpper is decimal upper)
		{
			var candidate = upper;
			if (BreakEvenPips > 0)
				candidate = Math.Min(candidate, entryPrice + breakEvenDistance);
			_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, candidate) : candidate;
		}

		if (UseAutoTargets && channelLower is decimal lower)
		{
			var candidate = lower;
			if (BreakEvenPips > 0)
				candidate = Math.Min(candidate, entryPrice - breakEvenDistance);
			_shortTake = _shortTake.HasValue ? Math.Min(_shortTake.Value, candidate) : candidate;
		}

		if (trailingDistance > 0m)
		{
			var candidate = candle.ClosePrice + trailingDistance;
			if (_shortStop is decimal currentStop)
			{
				if (currentStop - candidate >= Math.Max(trailingStep, _tickSize))
					_shortStop = candidate;
			}
			else
			{
				_shortStop = candidate;
			}
		}

		if (BreakEvenPips > 0 && !_shortBreakEvenActivated)
		{
			var activationPrice = entryPrice - breakEvenDistance - Math.Max(0m, trailingStep);
			var targetStop = entryPrice - breakEvenDistance;
			if (candle.ClosePrice <= activationPrice)
			{
				_shortBreakEvenActivated = true;
				_shortStop = _shortStop.HasValue ? Math.Min(_shortStop.Value, targetStop) : targetStop;
			}
		}
	}

	private void CheckExits(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longTake is decimal take && candle.HighPrice >= take)
			{
				SellMarket();
				ResetLongState();
			}
			else if (_longStop is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
				ResetLongState();
			}
		}
		else if (Position < 0)
		{
			if (_shortTake is decimal take && candle.LowPrice <= take)
			{
				BuyMarket();
				ResetShortState();
			}
			else if (_shortStop is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
				ResetShortState();
			}
		}
	}

	private decimal GetDistance(int pips)
	{
		return pips <= 0 ? 0m : pips * _tickSize;
	}

	private void ResetLongState()
	{
		_longStop = null;
		_longTake = null;
		_longEntryPrice = 0m;
		_longBreakEvenActivated = false;
	}

	private void ResetShortState()
	{
		_shortStop = null;
		_shortTake = null;
		_shortEntryPrice = 0m;
		_shortBreakEvenActivated = false;
	}

	private IIndicator CreateMovingAverage(MovingAverageModes mode, int length)
	{
		return mode switch
		{
			MovingAverageModes.Exponential => new ExponentialMovingAverage { Length = length },
			MovingAverageModes.Weighted => new WeightedMovingAverage { Length = length },
			MovingAverageModes.Smoothed => new SmoothedMovingAverage { Length = length },
			_ => new SimpleMovingAverage { Length = length },
		};
	}
}