Auf GitHub ansehen

RSI + 1200 Strategy

The RSI + 1200 Strategy seeks to capture trend reversals confirmed by relative strength and a higher time frame trend filter. It combines a classic 14‑period Relative Strength Index with an Exponential Moving Average calculated on a 120‑minute multi‑time frame series ("1200" refers to the higher time frame in the original concept). Trading signals are only taken when momentum and the trend filter align.

Backtests on liquid cryptocurrency pairs show that the method performs best in sustained directional markets. Choppy or range‑bound periods can produce false signals, so the strategy includes a small price slack around the EMA and a percentage based stop‑loss to help manage risk.

A long trade is opened when the RSI crosses upward from oversold territory and price is within one percent above the higher‑time‑frame EMA. The short setup is the mirrored condition. Positions are closed when the RSI reaches the opposite extreme, signalling exhaustion of the move. A protective stop is also placed at stopLossPercent percent from the entry price.

Details

  • Entry Conditions
    • Long: RSI crosses above rsiOversold and close is <= 1% above EMA.
    • Short: RSI crosses below rsiOverbought and close is >= 1% below EMA.
  • Exit Conditions
    • Long: RSI rises above rsiOverbought.
    • Short: RSI falls below rsiOversold.
  • Stops: Optional percentage stop‑loss via stopLossPercent.
  • Default Parameters
    • rsiLength = 14
    • rsiOverbought = 72
    • rsiOversold = 28
    • emaLength = 150
    • mtfTimeframe = 120 minutes
    • stopLossPercent = 0.10 (10%)
  • Filters
    • Category: Trend following
    • Direction: Both
    • Indicators: RSI, EMA
    • Stops: Yes
    • Complexity: Medium
    • Timeframe: Intraday / multi‑time frame
    • Seasonality: No
    • Neural networks: No
    • Divergence: No
    • Risk level: Moderate
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 + 1200 Strategy.
/// Uses RSI crossover signals with EMA trend filter.
/// Buys when RSI crosses above oversold level while price is above EMA.
/// Sells when RSI crosses below overbought level while price is below EMA.
/// </summary>
public class RsiPlus1200Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _rsiOverbought;
	private readonly StrategyParam<int> _rsiOversold;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _cooldownBars;

	private RelativeStrengthIndex _rsi;
	private ExponentialMovingAverage _ema;

	private decimal _prevRsi;
	private int _cooldownRemaining;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	public int RsiOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	public int RsiOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	public RsiPlus1200Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "RSI calculation length", "RSI");

		_rsiOverbought = Param(nameof(RsiOverbought), 70)
			.SetDisplay("RSI Overbought", "RSI overbought level", "RSI");

		_rsiOversold = Param(nameof(RsiOversold), 30)
			.SetDisplay("RSI Oversold", "RSI oversold level", "RSI");

		_emaLength = Param(nameof(EmaLength), 100)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA period for trend filter", "Moving Average");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
	}

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

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

		_rsi = null;
		_ema = null;
		_prevRsi = 0;
		_cooldownRemaining = 0;
	}

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

		_rsi = new RelativeStrengthIndex { Length = RsiLength };
		_ema = new ExponentialMovingAverage { Length = EmaLength };

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

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

	private void OnProcess(ICandleMessage candle, decimal rsiVal, decimal emaVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_rsi.IsFormed || !_ema.IsFormed)
		{
			_prevRsi = rsiVal;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevRsi = rsiVal;
			return;
		}

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevRsi = rsiVal;
			return;
		}

		if (_prevRsi == 0)
		{
			_prevRsi = rsiVal;
			return;
		}

		// RSI crossovers
		var rsiCrossUpOversold = rsiVal > RsiOversold && _prevRsi <= RsiOversold;
		var rsiCrossDownOverbought = rsiVal < RsiOverbought && _prevRsi >= RsiOverbought;

		// Buy: RSI crosses above oversold + price above EMA (uptrend)
		if (rsiCrossUpOversold && candle.ClosePrice > emaVal && Position <= 0)
		{
			if (Position < 0)
				BuyMarket(Math.Abs(Position));
			BuyMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		// Sell: RSI crosses below overbought + price below EMA (downtrend)
		else if (rsiCrossDownOverbought && candle.ClosePrice < emaVal && Position >= 0)
		{
			if (Position > 0)
				SellMarket(Math.Abs(Position));
			SellMarket(Volume);
			_cooldownRemaining = CooldownBars;
		}
		// Exit long: RSI overbought
		else if (Position > 0 && rsiVal > RsiOverbought)
		{
			SellMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}
		// Exit short: RSI oversold
		else if (Position < 0 && rsiVal < RsiOversold)
		{
			BuyMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}

		_prevRsi = rsiVal;
	}
}