Double RSI 戦略
Double RSI は、2つのRelative Strength Index計算を使用します:1つはトレードチャート上、 もう1つは上位時間軸上です。両方のRSI読みが同じ方向を支持する場合のみ取引が行われ、 短期エントリーを長期のモメンタムと合わせます。
メインの時間軸は、RSIが買われすぎゾーンや売られすぎゾーンからクロスするのを探します。 上位時間軸のRSIがその動きを確認すると、戦略はポジションを開きます。オプションの 利確設定で、事前に定義された動き後に利益を確定することができます。
詳細
- データ: 2つの時間軸の価格ローソク足。
- エントリー条件:
- ロング: 下位時間軸のRSIが売られすぎゾーンを抜け、かつ上位時間軸のRSIが強気。
- ショート: 下位時間軸のRSIが買われすぎゾーンを抜け、かつ上位時間軸のRSIが弱気。
- エグジット条件: 反対方向のRSIシグナル、または
UseTPが真の場合は利確。 - ストップ: デフォルトではなし。
- デフォルト値:
CandleType= tf(5)RSILength= 14MTFTimeframe= tf(15)UseTP= False
- フィルター:
- カテゴリ: モメンタム
- 方向: ロング/ショート
- インジケーター: RSI (multi‑timeframe)
- 複雑さ: 中程度
- リスクレベル: 中
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>
/// Double RSI strategy.
/// An RSI on the trading timeframe and another on MTFTimeframe. A long opens when the trading RSI crosses up out of
/// the oversold zone while the higher-timeframe RSI is rising (bullish); a short opens when it crosses down out of the
/// overbought zone while the higher-timeframe RSI is falling (bearish). The opposite RSI exit closes the position and an
/// optional percent take-profit locks in gains.
/// </summary>
public class DoubleRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<DataType> _mtfTimeframe;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<bool> _useTp;
private readonly StrategyParam<decimal> _takeProfitPercent;
private decimal? _prevRsi;
private decimal? _mtfRsi;
private decimal? _prevMtfRsi;
/// <summary>
/// Trading candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI period on both timeframes.
/// </summary>
public int RSILength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Higher timeframe for the confirming RSI.
/// </summary>
public DataType MTFTimeframe
{
get => _mtfTimeframe.Value;
set => _mtfTimeframe.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
/// <summary>
/// Enable the take-profit.
/// </summary>
public bool UseTP
{
get => _useTp.Value;
set => _useTp.Value = value;
}
/// <summary>
/// Take-profit percentage from the entry price.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DoubleRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle type", "Trading timeframe", "General");
_rsiLength = Param(nameof(RSILength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period on both timeframes", "RSI");
_mtfTimeframe = Param(nameof(MTFTimeframe), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("MTF Timeframe", "Higher timeframe for the confirming RSI", "RSI");
_oversold = Param(nameof(Oversold), 30m)
.SetDisplay("Oversold", "RSI oversold level", "RSI");
_overbought = Param(nameof(Overbought), 70m)
.SetDisplay("Overbought", "RSI overbought level", "RSI");
_useTp = Param(nameof(UseTP), false)
.SetDisplay("Use Take Profit", "Enable the percent take-profit", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take-profit percentage from the entry price", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, MTFTimeframe)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = null;
_mtfRsi = null;
_prevMtfRsi = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
_mtfRsi = null;
_prevMtfRsi = null;
var rsi = new RelativeStrengthIndex { Length = RSILength };
var mtfRsi = new RelativeStrengthIndex { Length = RSILength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ProcessCandle)
.Start();
SubscribeCandles(MTFTimeframe)
.BindEx(mtfRsi, ProcessMtfCandle)
.Start();
if (UseTP)
StartProtection(new Unit(TakeProfitPercent, UnitTypes.Percent), new Unit(), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var rsiArea = CreateChartArea();
if (rsiArea != null)
{
DrawIndicator(rsiArea, rsi);
DrawIndicator(rsiArea, mtfRsi);
}
}
}
private void ProcessMtfCandle(ICandleMessage candle, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
_prevMtfRsi = _mtfRsi;
_mtfRsi = rsiValue.ToDecimal();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!rsiValue.IsFormed)
return;
var rsi = rsiValue.ToDecimal();
var prevRsi = _prevRsi;
_prevRsi = rsi;
if (prevRsi is not decimal lastRsi || _mtfRsi is not decimal mtfRsi || _prevMtfRsi is not decimal prevMtfRsi)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var exitsOversold = lastRsi < Oversold && rsi >= Oversold;
var exitsOverbought = lastRsi > Overbought && rsi <= Overbought;
var longSignal = exitsOversold && mtfRsi > prevMtfRsi;
var shortSignal = exitsOverbought && mtfRsi < prevMtfRsi;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && exitsOverbought)
SellMarket(Position);
else if (Position < 0 && exitsOversold)
BuyMarket(-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
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class double_rsi_strategy(Strategy):
"""
Double RSI strategy.
An RSI on the trading timeframe and another on MTFTimeframe. A long opens when the trading RSI crosses up out of
the oversold zone while the higher-timeframe RSI is rising (bullish); a short opens when it crosses down out of the
overbought zone while the higher-timeframe RSI is falling (bearish). The opposite RSI exit closes the position and an
optional percent take-profit locks in gains.
"""
def __init__(self):
super(double_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle type", "Trading timeframe", "General")
self._rsi_length = self.Param("RSILength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period on both timeframes", "RSI")
self._mtf_timeframe = self.Param("MTFTimeframe", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("MTF Timeframe", "Higher timeframe for the confirming RSI", "RSI")
self._oversold = self.Param("Oversold", 30.0) \
.SetDisplay("Oversold", "RSI oversold level", "RSI")
self._overbought = self.Param("Overbought", 70.0) \
.SetDisplay("Overbought", "RSI overbought level", "RSI")
self._use_tp = self.Param("UseTP", False) \
.SetDisplay("Use Take Profit", "Enable the percent take-profit", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit %", "Take-profit percentage from the entry price", "Risk")
self._prev_rsi = None
self._mtf_rsi = None
self._prev_mtf_rsi = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def MTFTimeframe(self):
return self._mtf_timeframe.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, self.MTFTimeframe)]
def OnReseted(self):
super(double_rsi_strategy, self).OnReseted()
self._prev_rsi = None
self._mtf_rsi = None
self._prev_mtf_rsi = None
def OnStarted2(self, time):
super(double_rsi_strategy, self).OnStarted2(time)
self._prev_rsi = None
self._mtf_rsi = None
self._prev_mtf_rsi = None
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
mtf_rsi = RelativeStrengthIndex()
mtf_rsi.Length = self._rsi_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(rsi, self._process_candle).Start()
self.SubscribeCandles(self.MTFTimeframe).BindEx(mtf_rsi, self._process_mtf_candle).Start()
if self._use_tp.Value:
self.StartProtection(Unit(Decimal(self._take_profit_percent.Value), UnitTypes.Percent), Unit(), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
rsi_area = self.CreateChartArea()
if rsi_area is not None:
self.DrawIndicator(rsi_area, rsi)
self.DrawIndicator(rsi_area, mtf_rsi)
def _process_mtf_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
self._prev_mtf_rsi = self._mtf_rsi
self._mtf_rsi = float(rsi_value.GetValue[Decimal](None))
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
if not rsi_value.IsFormed:
return
rsi = float(rsi_value.GetValue[Decimal](None))
prev_rsi = self._prev_rsi
self._prev_rsi = rsi
if prev_rsi is None or self._mtf_rsi is None or self._prev_mtf_rsi is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
oversold = float(self._oversold.Value)
overbought = float(self._overbought.Value)
exits_oversold = prev_rsi < oversold and rsi >= oversold
exits_overbought = prev_rsi > overbought and rsi <= overbought
long_signal = exits_oversold and self._mtf_rsi > self._prev_mtf_rsi
short_signal = exits_overbought and self._mtf_rsi < self._prev_mtf_rsi
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and exits_overbought:
self.SellMarket(self.Position)
elif self.Position < 0 and exits_oversold:
self.BuyMarket(-self.Position)
def CreateClone(self):
return double_rsi_strategy()