GitHub で見る

Hpcs Inter6 RSI 戦略

概要

Hpcs Inter6 RSI 戦略は、MetaTrader エキスパート _HPCS_Inter6_MT4_EA_V01_WE を StockSharp のハイレベル API に移植します。このアルゴリズムは、設定可能なローソク足シリーズの相対強度指数 (RSI) を監視し、古典的な 70/30 のしきい値付近での急速な反転に反応します。 RSI が 70 を超えると、戦略はショート ポジションに切り替わり、30 を下回るクロスはロング ポジションに切り替わります。各取引は、ピップ単位で測定される対称的なテイクプロフィットレベルとストップロスレベルを即座に付加します。

データと指標

  • キャンドルソース – ユーザーが選択した時間枠 (デフォルトは 1 時間)。
  • インジケータ – 設定可能な長さの相対強度インデックス (デフォルトは 14)。インジケーターは、StockSharp インジケーター バインディング パイプラインを通じて再計算されます。

エントリーロジック

  1. この戦略は、不完全なデータでの取引を避けるために、ローソク足が完成するまで待ちます。
  2. 完了したローソク足ごとに、新しい RSI の値と前の値が比較されます。
  3. 簡単な設定: RSI が UpperLevel (デフォルト 70) を下から上に抜けたばかりの場合、ストラテジーは成行注文を使用して売ります。既存のロングエクスポージャーはショートが確立される前にクローズされるため、結果として生じるネットポジションは正確に設定された量だけショートになります。
  4. 長いセットアップ: RSI が上から LowerLevel (デフォルトは 30) を下回ったばかりの場合、この戦略は成行注文を使用して購入します。既存のショートが最初にカバーされるため、ネットポジションは設定されたボリュームによってロングになります。
  5. キャンドルごとに 1 回のエントリーのみが許可されます。同じバー内の複数の信号は、バーのタイムスタンプ ガードを使用する MetaTrader 実装を反映するために無視されます。

終了ロジック

  • すべてのエントリは固定ターゲットを定義し、ピップ単位で測定される同じ距離で停止します。
  • ロングポジションにあるとき、ローソク足の高値がターゲットに触れた場合、または安値が保護ストップに触れた場合、戦略は終了します。
  • ショートポジションにあるとき、戦略はローソク足の安値がターゲットに達するか、または高値が保護ストップに達するかどうかをカバーします。
  • 位置が平らになると、すべての保護レベルがクリアされます。

ピップ距離は、商品のティックサイズを使用して価格単位に変換されます。小数点以下 3 桁または 5 桁の金融商品の場合、アルゴリズムは距離を 10 倍して、1 ピップの MetaTrader の概念に一致させます。

パラメーター

パラメータ デフォルト 説明
CandleType 1時間枠 RSI インジケーターをフィードする時間枠。
RsiLength 14 RSI のルックバック期間。
UpperLevel 70 RSI レベルは、下からクロスされたときにショートエントリーをトリガーします。
LowerLevel 30 RSI レベルは、上からクロスするとロングエントリーをトリガーします。
TradeVolume 1 市場エントリーの注文サイズ。既存の露出は反転する前に閉じられます。
OffsetInPips 10 エントリー価格からのテイクプロフィットとストップロスの両方の距離をピップスで表します。

すべてのパラメータは StrategyParam オブジェクトを通じて公開されるため、StockSharp 内で最適化できます。

注意事項

  • この戦略は、ローソク足の高値/安値に基づいて利食いとストップロスの約定をシミュレートし、MetaTrader の固定価格ターゲットの動作と一致します。
  • 未決の注文は出されません。すべての約定は、ストラテジー コアによって処理される成行注文です。
  • チャート領域が利用可能になるとインジケーターとチャートのバインディングが自動的に作成され、ローソク足と RSI の視覚的なオーバーレイが提供されます。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader strategy _HPCS_Inter6_MT4_EA_V01_WE.
/// Trades RSI reversals at the 70/30 levels with symmetric fixed targets and stops.
/// </summary>
public class HpcsInter6RsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<decimal> _upperLevel;
	private readonly StrategyParam<decimal> _lowerLevel;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _offsetInPips;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private RelativeStrengthIndex _rsi;
	private decimal? _previousRsi;
	private DateTimeOffset? _lastSignalTime;
	private decimal? _targetPrice;
	private decimal? _stopPrice;
	private bool _isLongPosition;
	private int _candlesSinceTrade;

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

	/// <summary>
	/// RSI lookback length.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// Upper RSI level that triggers short entries when crossed from below.
	/// </summary>
	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	/// <summary>
	/// Lower RSI level that triggers long entries when crossed from above.
	/// </summary>
	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

	/// <summary>
	/// Market order volume used for entries.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Target and stop distance expressed in pips.
	/// </summary>
	public decimal OffsetInPips
	{
		get => _offsetInPips.Value;
		set => _offsetInPips.Value = value;
	}

	public int SignalCooldownCandles
	{
		get => _signalCooldownCandles.Value;
		set => _signalCooldownCandles.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="HpcsInter6RsiStrategy"/> class.
	/// </summary>
	public HpcsInter6RsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for RSI evaluation", "General");

		_rsiLength = Param(nameof(RsiLength), 7)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "Lookback period for RSI", "Parameters")

			.SetOptimize(5, 40, 1);

		_upperLevel = Param(nameof(UpperLevel), 65m)
			.SetDisplay("Upper RSI", "Upper RSI level for shorts", "Parameters")

			.SetOptimize(60m, 90m, 5m);

		_lowerLevel = Param(nameof(LowerLevel), 35m)
			.SetDisplay("Lower RSI", "Lower RSI level for longs", "Parameters")

			.SetOptimize(10m, 40m, 5m);

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume for entries", "Trading")
			
			.SetOptimize(0.1m, 5m, 0.1m);

		_offsetInPips = Param(nameof(OffsetInPips), 30m)
			.SetGreaterThanZero()
			.SetDisplay("Offset (pips)", "Target and stop distance in pips", "Risk")
			
			.SetOptimize(5m, 30m, 5m);

		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_rsi = null;
		_previousRsi = null;
		_lastSignalTime = null;
		_targetPrice = null;
		_stopPrice = null;
		_isLongPosition = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_previousRsi = null;
		_lastSignalTime = null;
		_targetPrice = null;
		_stopPrice = null;
		_isLongPosition = false;
		_candlesSinceTrade = SignalCooldownCandles;

		_rsi = new RelativeStrengthIndex
		{
			Length = RsiLength
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(_rsi, ProcessCandle)
			.Start();

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

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

		if (!_rsi.IsFormed)
			return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		UpdateActivePositionTargets(candle);

		var previousRsi = _previousRsi;
		_previousRsi = rsiValue;

		if (previousRsi is null)
			return;

		var candleTime = candle.OpenTime;

		if (_lastSignalTime.HasValue && _lastSignalTime.Value == candleTime)
			return;

		if (_candlesSinceTrade >= SignalCooldownCandles && TryEnterShort(candle, rsiValue, previousRsi.Value))
		{
			_lastSignalTime = candleTime;
			_candlesSinceTrade = 0;
			return;
		}

		if (_candlesSinceTrade >= SignalCooldownCandles && TryEnterLong(candle, rsiValue, previousRsi.Value))
		{
			_lastSignalTime = candleTime;
			_candlesSinceTrade = 0;
		}
	}

	private void UpdateActivePositionTargets(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (!_isLongPosition)
			{
				_targetPrice = null;
				_stopPrice = null;
				return;
			}

			var shouldExit = (_targetPrice.HasValue && candle.HighPrice >= _targetPrice.Value)
				|| (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value);

			if (shouldExit)
			{
				SellMarket(Math.Abs(Position));
				_targetPrice = null;
				_stopPrice = null;
			}
		}
		else if (Position < 0)
		{
			if (_isLongPosition)
			{
				_targetPrice = null;
				_stopPrice = null;
				return;
			}

			var shouldExit = (_targetPrice.HasValue && candle.LowPrice <= _targetPrice.Value)
				|| (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value);

			if (shouldExit)
			{
				BuyMarket(Math.Abs(Position));
				_targetPrice = null;
				_stopPrice = null;
			}
		}
		else
		{
			_targetPrice = null;
			_stopPrice = null;
			_isLongPosition = false;
		}
	}

	private bool TryEnterShort(ICandleMessage candle, decimal currentRsi, decimal previousRsi)
	{
		if (!(currentRsi > UpperLevel && previousRsi <= UpperLevel))
			return false;

		var volume = TradeVolume;
		if (volume <= 0m)
			return false;

		if (Position > 0)
		{
			volume += Math.Abs(Position);
		}

		SellMarket(volume);

		var offset = CalculateOffset();
		if (offset > 0m)
		{
			var entryPrice = candle.ClosePrice;
			_targetPrice = entryPrice - offset;
			_stopPrice = entryPrice + offset;
			_isLongPosition = false;
		}
		else
		{
			_targetPrice = null;
			_stopPrice = null;
			_isLongPosition = false;
		}

		return true;
	}

	private bool TryEnterLong(ICandleMessage candle, decimal currentRsi, decimal previousRsi)
	{
		if (!(currentRsi < LowerLevel && previousRsi >= LowerLevel))
			return false;

		var volume = TradeVolume;
		if (volume <= 0m)
			return false;

		if (Position < 0)
		{
			volume += Math.Abs(Position);
		}

		BuyMarket(volume);

		var offset = CalculateOffset();
		if (offset > 0m)
		{
			var entryPrice = candle.ClosePrice;
			_targetPrice = entryPrice + offset;
			_stopPrice = entryPrice - offset;
			_isLongPosition = true;
		}
		else
		{
			_targetPrice = null;
			_stopPrice = null;
			_isLongPosition = true;
		}

		return true;
	}

	private decimal CalculateOffset()
	{
		var priceStep = Security?.PriceStep ?? 0.01m;
		if (priceStep <= 0m)
			priceStep = 0.01m;

		var decimals = Security?.Decimals ?? 0;
		var factor = decimals is 3 or 5 ? 10m : 1m;

		return OffsetInPips * priceStep * factor;
	}
}