GitHub で見る

RSI Trader戦略

概要

この戦略は、MetaTraderのエキスパートアドバイザー*「RSI trader v0.15」*をStockSharp高レベルAPIで再現します。価格アクションと平滑化された相対力指数(RSI)の間でトレンド方向を整合させます。取引はデフォルトで1時間足を使用した単一の銘柄で行われますが、時間軸はCandleTypeパラメーターを通じて設定可能です。

取引ロジック

  1. 設定可能な期間で標準RSIを計算します。
  2. RSIを2つの単純移動平均(SMA)で平滑化します:速いシグナル平均と遅い確認平均。
  3. 終値の2つの移動平均を追跡します:短い単純移動平均と長い加重移動平均(元のMQL SMA/LWMAペアに近似)。
  4. 完成したローソク足ごとにトレンド状態を生成します:
    • 強気アライメント:短い価格SMAが長い価格SMAを上回るかつ速いRSI SMAが遅いRSI SMAを上回る。
    • 弱気アライメント:短い価格SMAが長い価格SMAを下回るかつ速いRSI SMAが遅いRSI SMAを下回る。
    • 横ばい/不一致:移動平均が逆方向を示しており、明確なトレンドがないことを示す。
  5. 検出された状態に基づき行動します:
    • 強気アライメントが現れ、現在ポジションが開いていない場合にロングポジションを開きます。
    • 弱気アライメントが現れ、現在ポジションが開いていない場合にショートポジションを開きます。
    • 横ばい状態が検出されたとき、開いているポジションを即座に閉じます(MQLバージョンの保護的終了を反映)。
  6. オプションのリバーサルモードはすべてのエントリー方向を反転させ、ユーザーが検出されたシグナルに対して逆張りで取引できるようにします。

この戦略はStockSharpの組み込み保護処理を尊重し、アクションを取る前に完成したローソク足を必要とします。

パラメーター

パラメーター 説明 デフォルト値
RsiPeriod RSI計算に使用するルックバック期間。 14
ShortRsiMaPeriod RSI値に適用される速いSMAの長さ。 9
LongRsiMaPeriod RSI値に適用される遅いSMAの長さ。 45
ShortPriceMaPeriod 終値に適用される短いSMAの長さ。 9
LongPriceMaPeriod 価格に適用される長い加重移動平均の長さ。 45
Reverse trueの場合、買いと売りの注文が入れ替わります(元の「Reverse」入力を反映)。 false
CandleType 価格ローソク足のデータタイプ。デフォルトは1時間時間軸。 1h

すべての整数パラメーターは、MetaTraderエキスパートの入力設定の柔軟性を反映した最適化範囲を公開します。

リスク管理

  • 価格とRSIのトレンドが一致しなくなると即座にポジションが閉じられ(横ばい状態)、EAの即時終了動作を再現します。
  • StartProtection()は起動時に有効化され、StockSharpの保護インフラストラクチャと連携します。

メモ

  • 戦略は取引サイズを定義するためにStrategyの基本Volumeプロパティに依存します。
  • 完成したローソク足のみが処理されます。部分的な更新は早期シグナルを避けるために無視されます。
  • 加重移動平均は、価格終値に適用された元の長いLWMAに一致させるために使用されます。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// RSI trader strategy aligning price and RSI moving-average trends.
/// </summary>
public class RsiTraderStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _shortRsiMaPeriod;
	private readonly StrategyParam<int> _longRsiMaPeriod;
	private readonly StrategyParam<int> _shortPriceMaPeriod;
	private readonly StrategyParam<int> _longPriceMaPeriod;
	private readonly StrategyParam<bool> _reverse;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _closes = new();
	private readonly List<decimal> _rsiValues = new();

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int ShortRsiMaPeriod { get => _shortRsiMaPeriod.Value; set => _shortRsiMaPeriod.Value = value; }
	public int LongRsiMaPeriod { get => _longRsiMaPeriod.Value; set => _longRsiMaPeriod.Value = value; }
	public int ShortPriceMaPeriod { get => _shortPriceMaPeriod.Value; set => _shortPriceMaPeriod.Value = value; }
	public int LongPriceMaPeriod { get => _longPriceMaPeriod.Value; set => _longPriceMaPeriod.Value = value; }
	public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public RsiTraderStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI calculation length", "RSI").SetGreaterThanZero();
		_shortRsiMaPeriod = Param(nameof(ShortRsiMaPeriod), 12).SetDisplay("Short RSI MA", "Short moving average on RSI", "RSI").SetGreaterThanZero();
		_longRsiMaPeriod = Param(nameof(LongRsiMaPeriod), 60).SetDisplay("Long RSI MA", "Long moving average on RSI", "RSI").SetGreaterThanZero();
		_shortPriceMaPeriod = Param(nameof(ShortPriceMaPeriod), 12).SetDisplay("Short Price MA", "Short simple moving average", "Price").SetGreaterThanZero();
		_longPriceMaPeriod = Param(nameof(LongPriceMaPeriod), 60).SetDisplay("Long Price MA", "Long weighted moving average", "Price").SetGreaterThanZero();
		_reverse = Param(nameof(Reverse), false).SetDisplay("Reverse", "Flip buy/sell signals", "Trading");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Primary candle type", "Data");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_closes.Clear();
		_rsiValues.Clear();
	}

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

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

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

		_closes.Add(candle.ClosePrice);

		var maxCache = Math.Max(LongPriceMaPeriod, Math.Max(LongRsiMaPeriod + RsiPeriod, 300));
		if (_closes.Count > maxCache)
			_closes.RemoveAt(0);

		var rsi = CalculateRsi();
		if (rsi is null)
			return;

		_rsiValues.Add(rsi.Value);
		if (_rsiValues.Count > maxCache)
			_rsiValues.RemoveAt(0);

		if (_rsiValues.Count < LongRsiMaPeriod || _closes.Count < LongPriceMaPeriod)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var shortRsi = AverageLast(_rsiValues, ShortRsiMaPeriod);
		var longRsi = AverageLast(_rsiValues, LongRsiMaPeriod);
		var shortPrice = AverageLast(_closes, ShortPriceMaPeriod);
		var longPrice = WeightedAverageLast(_closes, LongPriceMaPeriod);

		var goLong = shortPrice > longPrice && shortRsi > longRsi;
		var goShort = shortPrice < longPrice && shortRsi < longRsi;
		var sideways = !goLong && !goShort;

		if (sideways && Position != 0)
		{
			if (Position > 0)
				SellMarket(Position);
			else
				BuyMarket(Math.Abs(Position));

			return;
		}

		if (Position != 0)
			return;

		if (goLong)
		{
			if (Reverse)
				SellMarket();
			else
				BuyMarket();
		}
		else if (goShort)
		{
			if (Reverse)
				BuyMarket();
			else
				SellMarket();
		}
	}

	private decimal? CalculateRsi()
	{
		if (_closes.Count <= RsiPeriod)
			return null;

		decimal gainSum = 0m;
		decimal lossSum = 0m;
		var start = _closes.Count - RsiPeriod;

		for (var i = start; i < _closes.Count; i++)
		{
			var change = _closes[i] - _closes[i - 1];
			if (change > 0m)
				gainSum += change;
			else
				lossSum -= change;
		}

		var averageGain = gainSum / RsiPeriod;
		var averageLoss = lossSum / RsiPeriod;

		if (averageLoss == 0m)
			return 100m;

		var rs = averageGain / averageLoss;
		return 100m - 100m / (1m + rs);
	}

	private static decimal AverageLast(IReadOnlyList<decimal> values, int length)
	{
		decimal sum = 0m;
		var start = values.Count - length;

		for (var i = start; i < values.Count; i++)
			sum += values[i];

		return sum / length;
	}

	private static decimal WeightedAverageLast(IReadOnlyList<decimal> values, int length)
	{
		decimal weightedSum = 0m;
		decimal weightSum = 0m;
		var start = values.Count - length;
		var weight = 1m;

		for (var i = start; i < values.Count; i++)
		{
			weightedSum += values[i] * weight;
			weightSum += weight;
			weight += 1m;
		}

		return weightSum > 0m ? weightedSum / weightSum : values[^1];
	}
}