GitHub で見る

Reco RSIグリッド戦略

概要

この戦略は、StockSharpの高水準APIを使用して、MetaTraderのオリジナルエキスパートアドバイザー「Reco」の動作を再現します。アルゴリズムは相対力指数(RSI)に基づいて最初のポジションを建て、その後グリッドを形成する反対ポジションを配置します。グリッド注文間の距離とそのボリュームは幾何学的に増加します。累積損益が事前定義された閾値に達すると、すべての未決ポジションが一括でクローズされます。

トレードロジック

  • 初期シグナル – RSIが設定された買われすぎまたは売られすぎゾーンを超えます。RSIが売りレベルを上回ると空売りポジションが建てられ、買いレベルを下回ると買いポジションが建てられます。
  • グリッド拡張 – 最初の注文後、戦略は最後の取引に対する価格の動きを監視します。価格が計算された距離だけ動くと、反対の成行注文が送信されます。距離は各新規ステップで Distance Multiplier によって増加し、Max Distance および Min Distance で制限できます。
  • ボリュームスケーリング – 各新規注文のサイズは、初期 LotLot Multiplier を既に建てた注文数乗したものに等しくなります。最大・最小ボリューム制限もサポートされています。
  • エグジットルールUse Close Profit が有効な場合、集計利益が各追加注文の Profit Multiplier を乗じた Profit First Order を超えると全ポジションがクローズされます。Use Close Lose が有効な場合、同じロジックが Lose First OrderLose Multiplier を使用して損失に適用されます。

パラメーター

名前 説明
RsiPeriod RSIインジケーターの期間。
RsiSellZone 売りシグナルをトリガーするRSIレベル。
RsiBuyZone 買いシグナルをトリガーするRSIレベル。
StartDistance 最後の注文からの初期距離(ポイント単位)。
DistanceMultiplier 各追加注文の距離に適用される乗数。
MaxDistance 距離増加の上限(0で無効)。
MinDistance 距離増加の下限(0で無効)。
MaxOrders 同時に開ける最大注文数(0は制限なし)。
Lot 基本注文ボリューム。
LotMultiplier ボリュームスケーリングの乗数。
MaxLot 注文ごとの最大許容ボリューム(0で無効)。
MinLot 注文ごとの最小許容ボリューム(0で無効)。
UseCloseProfit 利益目標によるすべてのポジションのクローズを有効にする。
ProfitFirstOrder 最初の注文の利益目標。
ProfitMultiplier 後続注文の利益乗数。
UseCloseLose 損失閾値によるすべてのポジションのクローズを有効にする。
LoseFirstOrder 最初の注文の損失閾値。
LoseMultiplier 後続注文の損失乗数。
PointMultiplier 1ポイントを計算するために銘柄の価格刻みに適用される乗数。
CandleType インジケーター計算に使用するローソク足の種類。

注意事項

  • この戦略は成行注文を使用し、即時約定を前提としています。
  • ポジションはネット化されます:反対注文を建てると現在のポジションが減少または反転する場合があります。
  • この戦略はインデントにタブを使用し、プロジェクトの規則に従って英語のコメントを使用しています。
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>
/// RSI based grid strategy.
/// Opens the first trade when RSI reaches overbought/oversold zones
/// and then adds counter trades as price moves by configurable steps.
/// All positions are closed together on defined profit target.
/// </summary>
public class RecoRsiGridStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiSellZone;
	private readonly StrategyParam<decimal> _rsiBuyZone;
	private readonly StrategyParam<decimal> _gridStep;
	private readonly StrategyParam<int> _maxOrders;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _lastOrderPrice;
	private bool _lastOrderIsBuy;
	private int _ordersTotal;
	private decimal _entryPrice;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public decimal RsiSellZone { get => _rsiSellZone.Value; set => _rsiSellZone.Value = value; }
	public decimal RsiBuyZone { get => _rsiBuyZone.Value; set => _rsiBuyZone.Value = value; }
	public decimal GridStep { get => _gridStep.Value; set => _gridStep.Value = value; }
	public int MaxOrders { get => _maxOrders.Value; set => _maxOrders.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public RecoRsiGridStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI indicator period", "Signal");

		_rsiSellZone = Param(nameof(RsiSellZone), 70m)
			.SetDisplay("RSI Sell Zone", "RSI level to sell", "Signal");

		_rsiBuyZone = Param(nameof(RsiBuyZone), 30m)
			.SetDisplay("RSI Buy Zone", "RSI level to buy", "Signal");

		_gridStep = Param(nameof(GridStep), 200m)
			.SetDisplay("Grid Step", "Distance between grid orders", "Grid");

		_maxOrders = Param(nameof(MaxOrders), 5)
			.SetDisplay("Max Orders", "Maximum number of grid orders", "Grid");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_lastOrderPrice = 0;
		_lastOrderIsBuy = false;
		_ordersTotal = 0;
		_entryPrice = 0;
	}

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

		_lastOrderPrice = 0;
		_lastOrderIsBuy = false;
		_ordersTotal = 0;
		_entryPrice = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var price = candle.ClosePrice;

		// Check if we should close all on profit
		if (_ordersTotal > 0 && _entryPrice > 0)
		{
			var unrealized = Position > 0
				? price - _entryPrice
				: _entryPrice - price;

			if (unrealized > GridStep * 0.5m)
			{
				// Close all
				if (Position > 0)
					SellMarket();
				else if (Position < 0)
					BuyMarket();

				_ordersTotal = 0;
				_lastOrderPrice = 0;
				_entryPrice = 0;
				return;
			}
		}

		var signal = GetSignal(price, rsiValue);

		if (signal > 0 && Position <= 0)
		{
			BuyMarket();
			_lastOrderIsBuy = true;
			_lastOrderPrice = price;
			_entryPrice = price;
			_ordersTotal++;
		}
		else if (signal < 0 && Position >= 0)
		{
			SellMarket();
			_lastOrderIsBuy = false;
			_lastOrderPrice = price;
			_entryPrice = price;
			_ordersTotal++;
		}
	}

	private int GetSignal(decimal price, decimal rsiValue)
	{
		if (_ordersTotal == 0)
		{
			if (rsiValue >= RsiSellZone)
				return -1;
			if (rsiValue <= RsiBuyZone)
				return 1;
			return 0;
		}

		if (MaxOrders > 0 && _ordersTotal >= MaxOrders)
		{
			// Reset grid when max orders reached
			_ordersTotal = 0;
			_lastOrderPrice = 0;
			return 0;
		}

		// Add counter-trend orders at grid steps
		if (_lastOrderIsBuy && price <= _lastOrderPrice - GridStep)
			return 1;
		else if (!_lastOrderIsBuy && price >= _lastOrderPrice + GridStep)
			return -1;

		return 0;
	}
}