Dema RSI 策略
该策略交易双指数移动平均线的 RSI 与其平滑值的交叉。RSI 上穿平滑线时开多,下穿时开空。持仓可通过止盈、追踪止损和可选的交易时段过滤来保护。
参数
- K线类型
- 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。对已完成K线计算 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 决策时间判断;即使信号 K 线在时段内开盘,也不能在时段结束后入场。预热时的空 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()