GitHub で見る

RSI 充填ステップ戦略に関する RSI MA

概要

RSI 充填ステップ戦略**に関する **RSI MA は、MetaTrader エキスパート アドバイザー RSI_MAonRSI_Filling Step EA.mq5 の StockSharp 移植です。元のシステムは、相対強度指数 (RSI) で運動量を測定し、移動平均でその振動子を平滑化します。取引は、両方の値が中央 50 レベルの同じ側に留まりながら、RSI が移動平均を超えたときに開始されます。この変換では、構成可能な取引方向フィルター、オプションのセッション タイマー、および StockSharp の高レベルのインジケーター バインディングを活用しながらシグナルを反転する機能が維持されます。

取引ロジック

  1. 選択したローソク足シリーズをサブスクライブし、完成したすべてのバーで 2 つのインジケーターを計算します。長さが RsiPeriodRelativeStrengthIndex と、RSI ストリームに適用される MovingAverage (MaTypeMaPeriod) です。
  2. 行動する前に完全なローソク足を待ち、EA の「新しいバー」セーフガードを複製して、各バーが最大 1 つの取引決定を生成するようにします。
  3. 強気の設定は、以前の RSI の値が移動平均を下回り、最新の値が平均を上回って終了したが、両方の測定値が MiddleLevel (デフォルトは 50) を下回ったままである場合に発生します。 弱気の設定は、中間レベルより上のミラーケースです。
  4. ReverseSignals が有効な場合、強気条件はショート取引を生成し、弱気条件はロングトレードを生成し、EA の反転フラグを模倣します。
  5. Mode パラメータは、ロングのみ、ショートのみ、または両方向への取引を制限します。追加のガードはオプションで反対側のエクスポージャーを閉じ、ポジションがすでにオープンしている場合に新しいエントリーをブロックします。
  6. MetaTrader 実装と同じ毎日の時間枠では、深夜をまたぐセッションを含め、構成された SessionStartSessionEnd 間隔以外の信号を無効にすることができます。

パラメーター

  • CandleType – ストラテジーによって処理されたデータシリーズ。デフォルトは 1 時間のタイムフレームのローソク足です。
  • RsiPeriod – RSI の平均長 (デフォルトは 14)。
  • MaPeriod – RSI に適用される移動平均の長さ (デフォルトは 21)。
  • MaType – RSI の平滑化に使用される移動平均タイプ (デフォルトは Simple)。
  • MiddleLevel – 取引を検証するために両方のインジケーターで使用される中央の RSI レベル (デフォルトは 50)。
  • ReverseSignals – 強気/弱気クロスの解釈を反転します (デフォルトは false)。
  • モード – 取引方向フィルター (BuyOnlySellOnlyBoth)。
  • CloseOppositePositions – 新しい取引を開始する前に反対のポジションをフラットにするかどうか (デフォルトは false)。
  • OnlyOnePosition – ポジションがすでにオープンしている間は新しい注文を禁止します (デフォルトは false)。
  • UseTimeWindow – 毎日の取引セッション フィルターを有効にします (デフォルトは false)。
  • SessionStart / SessionEnd – 許可された取引セッションの開始時刻と終了時刻。真夜中を過ぎてラップすることで、夜間のセッションで動作します。

実装メモ

  • インジケーター値は Bind を通じて配信されるため、元の EA では CopyBuffer で必要だった手動のバッファ管理が不要になります。
  • 以前の RSI と移動平均値は、MQL からの RSI[m_bar_current+1] アクセス パターンを反映するためにキャッシュされます。 _lastSignalBarTime フィールドは、EA の m_last_deal_buy_in / m_last_deal_sell_in タイムスタンプと同様に、バーごとに 1 つの取引のみを保証します。
  • 取引管理は、BuyMarket()SellMarket() を使用して、EA の即時市場執行をミラーリングします。逆エクスポージャーのオプションの決済は、新しい注文を行う前に ClosePosition() で処理されます。
  • 時間フィルタは、開始時間が終了時間より大きい夜間ウィンドウ ロジックを含む、EA の TimeControlHourMinute 関数を複製します。
  • チャート ヘルパーは、トレード マーカーと専用の RSI パネルを使用して価格ローソク足を描画するため、バックテスト中にクロスオーバーを視覚的に検査できます。

Expert Advisor との違い

  • 資金管理オプション (ENUM_LOT_OR_RISK、トレーリング ストップ、フリーズ レベル チェック) は再現されません。 StockSharp ユーザーは、独自の保護ロジックまたはリスク モジュールを接続できます。
  • StockSharp は注文ライフサイクルを別の方法で管理するため、取引確認、マジック ナンバー チェック、および EA からの手動注文キューは不要です。この戦略は、成行注文が即時利用可能であることを前提としています。
  • ストップロス注文とテイクプロフィット注文は自動的には付加されません。その動作が必要な場合は、StartProtection または外部モジュールを使用してください。

使用のヒント

  1. 元の平均回帰動作を忠実に保つには、MiddleLevel を 50 に近づけてください。この値から逸脱すると、システムはブレイクアウト取引に向かうようになります。
  2. 厳密にフラットからポジションへの移行を希望する場合は、OnlyOnePosition を有効にします。カスタム ボリューム ロジックによるピラミッド化を許可するには、これを無効にします。
  3. 先物や株式を取引する場合は、夜間のノイズを避けるために、時間フィルターを取引所の取引時間と組み合わせます。
  4. 戦略を新しい商品に適応させる場合は、MaPeriodRsiPeriod、および MiddleLevel を一緒に最適化します。

これらのメモを使用すると、StockSharp 環境内で RSI の充填ステップ戦略の RSI MA を自信を持って実行、カスタマイズ、拡張できます。

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// RSI crossing its own moving average with trade signals.
/// Converted from the MetaTrader expert advisor "RSI_MAonRSI_Filling Step EA.mq5".
/// </summary>
public class RsiMaOnRsiFillingStepStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _maPeriod;

	private RelativeStrengthIndex _rsi;
	private readonly Queue<decimal> _rsiHistory = new();
	private decimal? _previousRsi;
	private decimal? _previousSignal;

	/// <summary>
	/// Initializes a new instance of <see cref="RsiMaOnRsiFillingStepStrategy"/>.
	/// </summary>
	public RsiMaOnRsiFillingStepStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe used for RSI calculations.", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "Number of bars for the RSI smoothing window.", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("MA Period", "Length of the moving average applied to the RSI.", "Indicators");
	}

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

	/// <summary>
	/// RSI averaging period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Moving average period applied to the RSI output.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_previousRsi = null;
		_previousSignal = null;
		_rsiHistory.Clear();
		_rsi = null!;
	}

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

		_previousRsi = null;
		_previousSignal = null;
		_rsiHistory.Clear();

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_rsi, ProcessCandle)
		.Start();

		var priceArea = CreateChartArea();
		if (priceArea != null)
		{
			DrawCandles(priceArea, subscription);
			DrawOwnTrades(priceArea);
		}
	}

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

		// Accumulate RSI values for moving average calculation
		_rsiHistory.Enqueue(rsiValue);
		while (_rsiHistory.Count > MaPeriod)
			_rsiHistory.Dequeue();

		if (!_rsi.IsFormed)
			return;

		// Need enough RSI values to compute the MA
		if (_rsiHistory.Count < MaPeriod)
		{
			_previousRsi = rsiValue;
			return;
		}

		// Calculate simple moving average of RSI
		var sum = 0m;
		var history = _rsiHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signalValue = sum / MaPeriod;

		if (_previousRsi is null || _previousSignal is null)
		{
			_previousRsi = rsiValue;
			_previousSignal = signalValue;
			return;
		}

		var crossUp = _previousRsi < _previousSignal && rsiValue > signalValue;
		var crossDown = _previousRsi > _previousSignal && rsiValue < signalValue;

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(volume);
		}

		_previousRsi = rsiValue;
		_previousSignal = signalValue;
	}
}