Стратегия 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 — минимальные шаги цены инструмента, а не проценты. Ненулевые значения включают нативный тейк-профит и трейлинг-стоп с абсолютным расстоянием, нуль отключает соответствующую защиту.
Фильтр сессии использует текущее время принятия решения в 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));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import process_float
class dema_rsi_strategy(Strategy):
"""RSI(DEMA) versus its own smoothing, with optional point protection and session."""
def __init__(self):
super(dema_rsi_strategy, self).__init__()
self._ma_length = self.Param("MaLength", 20) \
.SetGreaterThanZero().SetDisplay("MA Length", "DEMA EMA lengths", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero().SetDisplay("RSI Length", "RSI(DEMA) period", "Indicators")
self._rsi_smoothing_length = self.Param("RsiSmoothingLength", 5) \
.SetGreaterThanZero().SetDisplay("RSI Smoothing Length", "SMA of RSI", "Indicators")
self._take_profit_points = self.Param("TakeProfitPoints", 0.0) \
.SetRange(0.0, 100000.0).SetDisplay("Take Profit Points", "Price ticks, zero disables", "Risk")
self._trail_stop_points = self.Param("TrailStopPoints", 0.0) \
.SetRange(0.0, 100000.0).SetDisplay("Trail Stop Points", "Price ticks, zero disables", "Risk")
self._use_session = self.Param("UseSession", False) \
.SetDisplay("Use Session", "Limit entries to session", "Calendar")
self._session_start = self.Param("SessionStart", TimeSpan.FromHours(9)) \
.SetDisplay("Session Start", "Inclusive entry start", "Calendar")
self._session_end = self.Param("SessionEnd", TimeSpan.FromHours(17)) \
.SetDisplay("Session End", "Exclusive entry end", "Calendar")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Completed candle interval", "General")
self._ema1 = None
self._ema2 = None
self._rsi = None
self._smooth_rsi = None
self._previous_rsi = None
self._previous_smooth = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(dema_rsi_strategy, self).OnReseted()
self._ema1 = None
self._ema2 = None
self._rsi = None
self._smooth_rsi = None
self._previous_rsi = None
self._previous_smooth = None
def OnStarted2(self, time):
super(dema_rsi_strategy, self).OnStarted2(time)
start = self._session_start.Value
end = self._session_end.Value
if start < TimeSpan.Zero or start >= TimeSpan.FromDays(1) or end < TimeSpan.Zero or end >= TimeSpan.FromDays(1):
raise ValueError("Session times must be within one day")
length = int(self._ma_length.Value)
self._ema1 = ExponentialMovingAverage()
self._ema1.Length = length
self._ema2 = ExponentialMovingAverage()
self._ema2.Length = length
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._smooth_rsi = SimpleMovingAverage()
self._smooth_rsi.Length = int(self._rsi_smoothing_length.Value)
self._previous_rsi = None
self._previous_smooth = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._ema1, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema1)
self.DrawOwnTrades(area)
tick = float(self.Security.PriceStep) if self.Security is not None else 0.0
tp_points = float(self._take_profit_points.Value)
trail_points = float(self._trail_stop_points.Value)
if tick > 0 and (tp_points > 0 or trail_points > 0):
self.StartProtection(
Unit(tp_points * tick, UnitTypes.Absolute),
Unit(trail_points * tick, UnitTypes.Absolute),
trail_points > 0
)
def _is_in_session(self, time):
if not bool(self._use_session.Value):
return True
start = self._session_start.Value
end = self._session_end.Value
if start == end:
return True
if start < end:
return time >= start and time < end
return time >= start or time < end
def _process_candle(self, candle, first_ema):
if candle.State != CandleStates.Finished:
return
ema1 = float(first_ema)
ema2 = float(process_float(self._ema2, Decimal(ema1), candle.OpenTime, True))
dema = 2.0 * ema1 - ema2
rsi_value = process_float(self._rsi, Decimal(dema), candle.OpenTime, True)
if rsi_value.IsEmpty:
return
rsi = float(rsi_value)
smoothed = float(process_float(self._smooth_rsi, Decimal(rsi), candle.OpenTime, True))
cross_up = (
self._previous_rsi is not None and self._previous_smooth is not None and
self._previous_rsi <= self._previous_smooth and rsi > smoothed
)
cross_down = (
self._previous_rsi is not None and self._previous_smooth is not None and
self._previous_rsi >= self._previous_smooth and rsi < smoothed
)
self._previous_rsi = rsi
self._previous_smooth = smoothed
if not self._ema1.IsFormed or not self._ema2.IsFormed or not self._rsi.IsFormed or not self._smooth_rsi.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading() or not self._is_in_session(self.CurrentTime.TimeOfDay):
return
if cross_up and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif cross_down and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def CreateClone(self):
return dema_rsi_strategy()