GitHub で見る

XD レンジスイッチ戦略

概要

XD レンジスイッチ戦略は、StockSharpの高レベルAPIを使用してMetaTrader 5のエキスパートアドバイザーExp_XD-RangeSwitchを再現します。カスタムのXD-RangeSwitchチャネルインジケーターに依存し、支配的なバンドが反転するたびに交互の上下バンドとともに矢印を描画します。この戦略はTradeDirectionパラメーターに応じて、これらの矢印をフェードアウト(逆張り動作)するか、ブレイクアウトの方向でトレードするかを選択できます。注文サイジングは基本のStrategy.Volume設定に従い、元のマネー管理フォーミュラはStockSharpのポジション管理ヘルパーに置き換えられます。

XD-RangeSwithcインジケーターの再現

  • インジケーターは最後のPeaksの完了ローソク足を追跡して、最高値と最低値の範囲を決定します。
  • 現在のクローズが前のPeaksバーの最高高値を上回る場合、強気チャネル(下部バンド)が描画されます。その値は同じウィンドウにおける最低安値と現在のバーを合わせたものに等しいです。
  • 現在のクローズが前のPeaksバーの最低安値を下回る場合、弱気チャネル(上部バンド)が描画されます。その値は同じウィンドウにおける最高高値と現在のバーを合わせたものに等しいです。
  • ブレイクアウトが発生しない場合、前のチャネル値が前方に伝播されます。
  • チャネルが空の後に再び現れるたびに、戦略はチャネル価格に矢印シグナルを記録します。これは元のエキスパートが使用するMT5バッファ2と3の動作を反映します。
  • 完全に終了したローソク足のみが処理され、ライブ実行と過去の実行で一貫した値を確保します。

トレードロジック

  1. 戦略はCandleTypeで選択された時間軸からローソク足を処理し、再構築されたインジケーターバッファを格納します。
  2. 各新しいローソク足について、SignalBar本分だけ古いインジケーター値を確認します(MT5コードはCopyBufferを呼び出す際に同じオフセットを使用します)。
  3. シグナルのマッピングはTradeDirectionオプションに依存します:
    • AgainstSignalはデフォルトのMT5動作を再現します — 強気の矢印がロングを起動し、ショートトレードの決済も要求します;弱気の矢印がショートを起動し、ロングの決済を要求します。
    • WithSignalは解釈を反転させ、強気の矢印がロングの出口ポイントとショートの入口ポイントとして扱われ、実質的にチャネルブレイクアウトと同じ方向でトレードします。
  4. 矢印のないトレンドバッファは依然として出口シグナルとして尊重され、元のSELL_CloseおよびBUY_Closeフラグと一致します。
  5. クローズは常にオープンの前に実行され、戦略が新しい方向に入る前に反対のポジションをフラットにできます。
  6. 注文は成行執行ヘルパー(BuyMarket/SellMarket)で提出されます。反対のポジションが開いている間にフリップが発生すると、新しいポジションを確立する前にエクスポージャーを完全にオフセットするために要求された数量が自動的に増加します。

リスク管理

  • オプションのストップロスとテイクプロフィットのロジックはUseStopLoss/StopLossPointsおよびUseTakeProfit/TakeProfitPointsパラメーターを通じて提供されます。
  • 距離は絶対価格単位で測定され、MT5スクリプトの「ポイント」入力を反映します。
  • ストップとターゲットはバー内トリガーをエミュレートするためにローソク足の高値/安値を使用して、各完了ローソク足で評価されます。
  • ストップとターゲットの両方がアクティブな場合、ストップが優先されます — いずれかのレベルに達するとポジションが決済されます。

パラメーター

名前 デフォルト 説明
CandleType H4ローソク足 XD-RangeSwitch計算に使用される時間軸。
Peaks 4 インジケーターが分析するピーク数(ルックバック長さ)。
SignalBar 1 インジケーターバッファを読み取る際の完了バー数の遡り。
TradeDirection AgainstSignal シグナルの逆張りまたはトレンドフォロー解釈を選択します。
AllowBuyEntry / AllowSellEntry true 対応する方向での新規ポジション開設を有効または無効にします。
AllowBuyExit / AllowSellExit true インジケーターが要求したときに戦略が既存ポジションを決済することを許可します。
UseStopLoss / StopLossPoints true / 1000 ストップロス処理を有効にし、価格単位での距離を定義します。
UseTakeProfit / TakeProfitPoints true / 2000 テイクプロフィット処理を有効にし、価格単位での距離を定義します。

注意事項

  • 高値/安値バッファはStockSharpコレクションに頼るのではなく、戦略内部で管理され、変換ガイドラインに準拠しながらMT5実装に忠実に保ちます。
  • シグナルは完了したローソク足のみで評価されます。SignalBarがゼロより大きい場合、注文はMT5エキスパートと同様に、シグナルを生成したローソク足の次のローソク足に配置されます。
  • インジケーター値はPeaksSignalBarの最大値を若干超える短いローリングヒストリーに保持され、長いシミュレーション中でも確定的なメモリ使用を確保します。
  • デフォルト設定はMT5のデフォルトを反映します:H4ローソク足、Peaks = 4SignalBar = 1、逆張りトレード、および1,000/2,000ポイントのリスクエンベロープ。
using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// XD-RangeSwitch strategy using Highest/Lowest channel breakouts.
/// Buys on breakout above channel high, sells on breakout below channel low.
/// Uses stop-loss and take-profit for risk management.
/// </summary>
public class XdRangeSwitchStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private Highest _highest;
	private Lowest _lowest;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _entryPrice;
	private int _cooldown;

	/// <summary>
	/// Channel lookback period.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take-profit distance in price steps.
	/// </summary>
	public int TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Initializes strategy parameters.
	/// </summary>
	public XdRangeSwitchStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 200)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Lookback for highest/lowest channel", "Indicator");

		_stopLossPoints = Param(nameof(StopLossPoints), 200)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop-loss distance in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take-profit distance in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

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

		_highest = null;
		_lowest = null;
		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
		_cooldown = 0;
	}

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

		_highest = new Highest { Length = ChannelPeriod };
		_lowest = new Lowest { Length = ChannelPeriod };

		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_highest, _lowest, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal highValue, decimal lowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_highest.IsFormed || !_lowest.IsFormed)
		{
			_prevHigh = highValue;
			_prevLow = lowValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevHigh = highValue;
			_prevLow = lowValue;
			return;
		}

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		// Check SL/TP
		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step)
			{
				SellMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}

			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step)
			{
				BuyMarket();
				_entryPrice = 0;
				_cooldown = 30;
				_prevHigh = highValue;
				_prevLow = lowValue;
				return;
			}
		}

		// Breakout above previous channel high
		if (close > _prevHigh && _prevHigh > 0 && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();

			BuyMarket();
			_entryPrice = close;
			_cooldown = 30;
		}
		// Breakout below previous channel low
		else if (close < _prevLow && _prevLow > 0 && Position >= 0)
		{
			if (Position > 0)
				SellMarket();

			SellMarket();
			_entryPrice = close;
			_cooldown = 30;
		}

		_prevHigh = highValue;
		_prevLow = lowValue;
	}
}