View on GitHub

Dema RSI Strategy

This strategy trades crossovers between the RSI of a double exponential moving average and its smoothed value. A long position opens when the RSI crosses above the smoothed line and a short position opens on the opposite crossover. Positions can be protected with take profit, trailing stop and an optional trading session filter.

Parameters

  • Candle Type
  • MA length
  • RSI length
  • RSI smoothing length
  • Take profit points
  • Trail stop points
  • Use session
  • Session start
  • Session end

Executed RSI(DEMA) contract

Defaults: CandleType=1m, MaLength=20, RsiLength=14, RsiSmoothingLength=5, TakeProfitPoints=0, TrailStopPoints=0, UseSession=false, SessionStart=09:00, SessionEnd=17:00. For each finished candle calculate EMA1=EMA(Close,20), EMA2=EMA(EMA1,20), DEMA=2×EMA1−EMA2, R=RSI(DEMA,14), S=SMA(R,5). Buy when the previous R <= S and current R > S, if flat or short. Sell when previous R >= S and current R < S, if flat or long. A reversal sizes the order Volume + abs(Position). Optional UseSession=true applies the entry filter to the half-open interval [SessionStart,SessionEnd); overnight intervals are supported and protection exits are not time-restricted. TakeProfitPoints and TrailStopPoints are instrument minimum-price ticks: nonzero settings use native absolute-distance take-profit and trailing stop, zero disables that leg.

The session filter uses the current UTC decision time, so an order cannot enter after the session ends even when its candle opened earlier. Empty RSI values during warm-up are not fed into the smoothing average.

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>
/// Trade strict crossings of RSI computed from a double exponential moving
/// average (DEMA) and its own SMA, with optional session and point protections.
/// </summary>
public class DemaRsiStrategy : Strategy
{
    private readonly StrategyParam<int> _maLength;
    private readonly StrategyParam<int> _rsiLength;
    private readonly StrategyParam<int> _rsiSmoothingLength;
    private readonly StrategyParam<decimal> _takeProfitPoints;
    private readonly StrategyParam<decimal> _trailStopPoints;
    private readonly StrategyParam<bool> _useSession;
    private readonly StrategyParam<TimeSpan> _sessionStart;
    private readonly StrategyParam<TimeSpan> _sessionEnd;
    private readonly StrategyParam<DataType> _candleType;

    private ExponentialMovingAverage _ema1;
    private ExponentialMovingAverage _ema2;
    private RelativeStrengthIndex _rsi;
    private SimpleMovingAverage _smoothRsi;
    private decimal? _previousRsi;
    private decimal? _previousSmooth;

    public int MaLength { get => _maLength.Value; set => _maLength.Value = value; }
    public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
    public int RsiSmoothingLength { get => _rsiSmoothingLength.Value; set => _rsiSmoothingLength.Value = value; }
    public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
    public decimal TrailStopPoints { get => _trailStopPoints.Value; set => _trailStopPoints.Value = value; }
    public bool UseSession { get => _useSession.Value; set => _useSession.Value = value; }
    public TimeSpan SessionStart { get => _sessionStart.Value; set => _sessionStart.Value = value; }
    public TimeSpan SessionEnd { get => _sessionEnd.Value; set => _sessionEnd.Value = value; }
    public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

    public DemaRsiStrategy()
    {
        _maLength = Param(nameof(MaLength), 20)
            .SetGreaterThanZero().SetDisplay("MA Length", "DEMA first/second EMA length", "Indicators");
        _rsiLength = Param(nameof(RsiLength), 14)
            .SetGreaterThanZero().SetDisplay("RSI Length", "RSI of DEMA", "Indicators");
        _rsiSmoothingLength = Param(nameof(RsiSmoothingLength), 5)
            .SetGreaterThanZero().SetDisplay("RSI Smoothing Length", "SMA of RSI(DEMA)", "Indicators");
        _takeProfitPoints = Param(nameof(TakeProfitPoints), 0m)
            .SetRange(0m, 100000m).SetDisplay("Take Profit Points", "Take profit in security price ticks; zero disables", "Risk");
        _trailStopPoints = Param(nameof(TrailStopPoints), 0m)
            .SetRange(0m, 100000m).SetDisplay("Trail Stop Points", "Trailing stop in security price ticks; zero disables", "Risk");
        _useSession = Param(nameof(UseSession), false)
            .SetDisplay("Use Session", "Restrict signal entries to a time window", "Calendar");
        _sessionStart = Param(nameof(SessionStart), TimeSpan.FromHours(9))
            .SetDisplay("Session Start", "Inclusive start of entry session", "Calendar");
        _sessionEnd = Param(nameof(SessionEnd), TimeSpan.FromHours(17))
            .SetDisplay("Session End", "Exclusive end of entry session", "Calendar");
        _candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
            .SetDisplay("Candle Type", "Completed candle interval", "General");
    }

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

    protected override void OnReseted()
    {
        base.OnReseted();
        _ema1 = null;
        _ema2 = null;
        _rsi = null;
        _smoothRsi = null;
        _previousRsi = null;
        _previousSmooth = null;
    }

    protected override void OnStarted2(DateTime time)
    {
        base.OnStarted2(time);
        if (SessionStart < TimeSpan.Zero || SessionStart >= TimeSpan.FromDays(1) ||
            SessionEnd < TimeSpan.Zero || SessionEnd >= TimeSpan.FromDays(1))
            throw new ArgumentException("Session times must be within one day.");

        _ema1 = new ExponentialMovingAverage { Length = MaLength };
        _ema2 = new ExponentialMovingAverage { Length = MaLength };
        _rsi = new RelativeStrengthIndex { Length = RsiLength };
        _smoothRsi = new SimpleMovingAverage { Length = RsiSmoothingLength };
        _previousRsi = null;
        _previousSmooth = null;

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

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

        // Protection uses ABSOLUTE price distance in minimum price ticks, not percent.
        var tick = Security?.PriceStep ?? 0m;
        if (tick > 0m && (TakeProfitPoints > 0m || TrailStopPoints > 0m))
        {
            StartProtection(
                new Unit(TakeProfitPoints * tick, UnitTypes.Absolute),
                new Unit(TrailStopPoints * tick, UnitTypes.Absolute),
                TrailStopPoints > 0m);
        }
    }

    private bool IsInSession(TimeSpan time)
    {
        if (!UseSession || SessionStart == SessionEnd)
            return true;

        return SessionStart < SessionEnd
            ? time >= SessionStart && time < SessionEnd
            : time >= SessionStart || time < SessionEnd;
    }

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

        var secondEma = _ema2.Process(firstEma, candle.OpenTime, true).ToDecimal();
        var dema = 2m * firstEma - secondEma;
        var rsiValue = _rsi.Process(dema, candle.OpenTime, true);
        if (rsiValue.IsEmpty)
            return;
        var rsi = rsiValue.ToDecimal();
        var smoothed = _smoothRsi.Process(rsi, candle.OpenTime, true).ToDecimal();

        var crossUp = _previousRsi is decimal pr && _previousSmooth is decimal ps &&
            pr <= ps && rsi > smoothed;
        var crossDown = _previousRsi is decimal qr && _previousSmooth is decimal qs &&
            qr >= qs && rsi < smoothed;

        _previousRsi = rsi;
        _previousSmooth = smoothed;

        if (!_ema1.IsFormed || !_ema2.IsFormed || !_rsi.IsFormed || !_smoothRsi.IsFormed ||
            !IsFormedAndOnlineAndAllowTrading() || !IsInSession(CurrentTime.TimeOfDay))
            return;

        if (crossUp && Position <= 0m)
            BuyMarket(Volume + Math.Abs(Position));
        else if (crossDown && Position >= 0m)
            SellMarket(Volume + Math.Abs(Position));
    }
}