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