GitHub で見る

DEMA RSI戦略

この戦略は二重指数移動平均のRSIとその平滑化値のクロスオーバーを取引します。RSIが平滑化ラインを上抜けたときにロングポジションを建て、逆のクロスオーバーでショートポジションを建てます。ポジションはテイクプロフィット、トレーリングストップ、およびオプションの取引セッションフィルターで保護できます。

パラメーター

  • ローソク足タイプ
  • MA期間
  • RSI期間
  • RSI平滑化期間
  • テイクプロフィット(ポイント)
  • トレーリングストップ(ポイント)
  • セッションを使用する
  • セッション開始
  • セッション終了

RSI(DEMA) の実際の売買条件

既定値: CandleType=1m、MaLength=20、RsiLength=14、RsiSmoothingLength=5、TakeProfitPoints=0、TrailStopPoints=0、UseSession=false、SessionStart=09:00、SessionEnd=17:00。確定足から EMA1=EMA(Close,20)、EMA2=EMA(EMA1,20)、DEMA=2×EMA1−EMA2、R=RSI(DEMA,14)、S=SMA(R,5) を計算します。前回 R <= S から今回 R > S に抜けたら買い、前回 R >= S から今回 R < S なら売り。ノーポジションからの新規、または Volume + abs(Position) による反転です。UseSession=true ならエントリーは [SessionStart,SessionEnd) に限定(深夜越え対応)、保護決済は時間制限を受けません。TakeProfitPoints と TrailStopPoints は商品の最小価格刻み単位で、正の値はネイティブの絶対値幅による利確とトレーリング損切りを有効化し、0は各機能を無効化します。

セッション判定には現在の UTC 判断時刻を使います。シグナル足がセッション内に始まっていても、終了後はエントリーできません。ウォームアップ中の空の RSI 値は平滑平均へ渡しません。

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));
    }
}