デュアル RSI ディファレンシャル戦略
デュアル RSI ディファレンシャルは 2 つの RSI 期間を比較し、その差が閾値を越えたときに取引します。この二重期間アプローチは、短期および長期モメンタムの乖離を捉えることを目的としています。
詳細
- データ: 価格ローソク足。
- エントリー条件:
- ロング:
RSI(Long) - RSI(Short)<RsiDiffLevel。 - ショート:
RSI(Long) - RSI(Short)>RsiDiffLevel。
- ロング:
- エグジット条件: 逆方向の閾値、オプションの保有期間、オプションのテイクプロフィット/ストップロス。
- ストップ: オプションのテイクプロフィットとストップロス(
Condition)。 - デフォルト値:
ShortRsiPeriod= 21LongRsiPeriod= 42RsiDiffLevel= 5UseHoldDays= TrueHoldDays= 5Condition= NoneTakeProfitPerc= 15StopLossPerc= 10
- フィルター:
- カテゴリ: モメンタム
- 方向: ロングとショート
- インジケーター: 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>
/// Dual RSI Differential strategy.
/// The differential is RSI(LongRsiPeriod) minus RSI(ShortRsiPeriod). When it crosses below RsiDiffLevel the strategy goes long and
/// when it crosses above RsiDiffLevel it goes short, reversing an opposite position. With UseHoldDays a position is closed after
/// HoldDays days. Condition selects the sides that use the TakeProfitPerc and StopLossPerc exits.
/// </summary>
public class DualRsiDifferentialStrategy : Strategy
{
/// <summary>
/// Sides that use take profit and stop loss.
/// </summary>
public enum Conditions
{
/// <summary>
/// No take profit or stop loss.
/// </summary>
None,
/// <summary>
/// Long positions only.
/// </summary>
Long,
/// <summary>
/// Short positions only.
/// </summary>
Short,
/// <summary>
/// Both sides.
/// </summary>
Both,
}
private readonly StrategyParam<int> _shortRsiPeriod;
private readonly StrategyParam<int> _longRsiPeriod;
private readonly StrategyParam<decimal> _rsiDiffLevel;
private readonly StrategyParam<bool> _useHoldDays;
private readonly StrategyParam<int> _holdDays;
private readonly StrategyParam<Conditions> _condition;
private readonly StrategyParam<decimal> _takeProfitPerc;
private readonly StrategyParam<decimal> _stopLossPerc;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevDiff;
private decimal _entryPrice;
private DateTime _entryTime;
/// <summary>
/// Short RSI period.
/// </summary>
public int ShortRsiPeriod
{
get => _shortRsiPeriod.Value;
set => _shortRsiPeriod.Value = value;
}
/// <summary>
/// Long RSI period.
/// </summary>
public int LongRsiPeriod
{
get => _longRsiPeriod.Value;
set => _longRsiPeriod.Value = value;
}
/// <summary>
/// Threshold of the RSI differential.
/// </summary>
public decimal RsiDiffLevel
{
get => _rsiDiffLevel.Value;
set => _rsiDiffLevel.Value = value;
}
/// <summary>
/// Close positions after the holding period.
/// </summary>
public bool UseHoldDays
{
get => _useHoldDays.Value;
set => _useHoldDays.Value = value;
}
/// <summary>
/// Holding period in days.
/// </summary>
public int HoldDays
{
get => _holdDays.Value;
set => _holdDays.Value = value;
}
/// <summary>
/// Sides that use take profit and stop loss.
/// </summary>
public Conditions Condition
{
get => _condition.Value;
set => _condition.Value = value;
}
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal TakeProfitPerc
{
get => _takeProfitPerc.Value;
set => _takeProfitPerc.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPerc
{
get => _stopLossPerc.Value;
set => _stopLossPerc.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DualRsiDifferentialStrategy()
{
_shortRsiPeriod = Param(nameof(ShortRsiPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Short RSI", "Short RSI period", "Indicators");
_longRsiPeriod = Param(nameof(LongRsiPeriod), 42)
.SetGreaterThanZero()
.SetDisplay("Long RSI", "Long RSI period", "Indicators");
_rsiDiffLevel = Param(nameof(RsiDiffLevel), 5m)
.SetDisplay("RSI Diff Level", "Threshold of the RSI differential", "Indicators");
_useHoldDays = Param(nameof(UseHoldDays), true)
.SetDisplay("Use Hold Days", "Close positions after the holding period", "Exit");
_holdDays = Param(nameof(HoldDays), 5)
.SetGreaterThanZero()
.SetDisplay("Hold Days", "Holding period in days", "Exit");
_condition = Param(nameof(Condition), Conditions.None)
.SetDisplay("Condition", "Sides that use take profit and stop loss", "Risk");
_takeProfitPerc = Param(nameof(TakeProfitPerc), 15m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk");
_stopLossPerc = Param(nameof(StopLossPerc), 10m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevDiff = null;
_entryPrice = 0m;
_entryTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var shortRsi = new RelativeStrengthIndex { Length = ShortRsiPeriod };
var longRsi = new RelativeStrengthIndex { Length = LongRsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(shortRsi, longRsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, shortRsi);
DrawIndicator(oscillators, longRsi);
}
}
}
private bool UsesRiskExits(bool isLong)
{
return Condition == Conditions.Both || (isLong ? Condition == Conditions.Long : Condition == Conditions.Short);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue shortValue, IIndicatorValue longValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!shortValue.IsFormed || !longValue.IsFormed)
return;
var diff = longValue.GetValue<decimal>() - shortValue.GetValue<decimal>();
var prevDiff = _prevDiff;
_prevDiff = diff;
if (prevDiff is not decimal prev)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (Position != 0)
{
var isLong = Position > 0;
var exit = UseHoldDays && candle.CloseTime - _entryTime >= TimeSpan.FromDays(HoldDays);
if (!exit && UsesRiskExits(isLong) && _entryPrice > 0)
{
var take = TakeProfitPerc / 100m;
var stop = StopLossPerc / 100m;
if (isLong)
exit = (take > 0 && candle.HighPrice >= _entryPrice * (1 + take)) || (stop > 0 && candle.LowPrice <= _entryPrice * (1 - stop));
else
exit = (take > 0 && candle.LowPrice <= _entryPrice * (1 - take)) || (stop > 0 && candle.HighPrice >= _entryPrice * (1 + stop));
}
if (exit)
{
if (isLong)
SellMarket(Position);
else
BuyMarket(-Position);
return;
}
}
var longSignal = prev >= RsiDiffLevel && diff < RsiDiffLevel;
var shortSignal = prev <= RsiDiffLevel && diff > RsiDiffLevel;
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_entryPrice = close;
_entryTime = candle.CloseTime;
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_entryPrice = close;
_entryTime = candle.CloseTime;
}
}
}
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
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class Conditions:
"""Sides that use take profit and stop loss."""
NONE = 0
Long = 1
Short = 2
Both = 3
class dual_rsi_differential_strategy(Strategy):
"""
Dual RSI Differential strategy.
The differential is RSI(LongRsiPeriod) minus RSI(ShortRsiPeriod). When it crosses below RsiDiffLevel the strategy goes long and
when it crosses above RsiDiffLevel it goes short, reversing an opposite position. With UseHoldDays a position is closed after
HoldDays days. Condition selects the sides that use the TakeProfitPerc and StopLossPerc exits.
"""
def __init__(self):
super(dual_rsi_differential_strategy, self).__init__()
self._short_rsi_period = self.Param("ShortRsiPeriod", 21).SetGreaterThanZero().SetDisplay("Short RSI", "Short RSI period", "Indicators")
self._long_rsi_period = self.Param("LongRsiPeriod", 42).SetGreaterThanZero().SetDisplay("Long RSI", "Long RSI period", "Indicators")
self._rsi_diff_level = self.Param("RsiDiffLevel", 5.0).SetDisplay("RSI Diff Level", "Threshold of the RSI differential", "Indicators")
self._use_hold_days = self.Param("UseHoldDays", True).SetDisplay("Use Hold Days", "Close positions after the holding period", "Exit")
self._hold_days = self.Param("HoldDays", 5).SetGreaterThanZero().SetDisplay("Hold Days", "Holding period in days", "Exit")
self._condition = self.Param("Condition", Conditions.NONE).SetDisplay("Condition", "Sides that use take profit and stop loss", "Risk")
self._take_profit_perc = self.Param("TakeProfitPerc", 15.0).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage from entry price", "Risk")
self._stop_loss_perc = self.Param("StopLossPerc", 10.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_diff = None
self._entry_price = Decimal(0)
self._entry_time = None
def OnReseted(self):
super(dual_rsi_differential_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(dual_rsi_differential_strategy, self).OnStarted2(time)
self._reset_state()
short_rsi = RelativeStrengthIndex()
short_rsi.Length = self._short_rsi_period.Value
long_rsi = RelativeStrengthIndex()
long_rsi.Length = self._long_rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(short_rsi, long_rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, short_rsi)
self.DrawIndicator(oscillators, long_rsi)
def _uses_risk_exits(self, is_long):
condition = int(self._condition.Value)
if condition == Conditions.Both:
return True
return condition == (Conditions.Long if is_long else Conditions.Short)
def _process_candle(self, candle, short_value, long_value):
if candle.State != CandleStates.Finished:
return
if not short_value.IsFormed or not long_value.IsFormed:
return
diff = long_value.GetValue[Decimal](None) - short_value.GetValue[Decimal](None)
prev = self._prev_diff
self._prev_diff = diff
if prev is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
if self.Position != 0:
is_long = self.Position > 0
exit_now = self._use_hold_days.Value and self._entry_time is not None and \
candle.CloseTime - self._entry_time >= TimeSpan.FromDays(self._hold_days.Value)
if not exit_now and self._uses_risk_exits(is_long) and self._entry_price > 0:
take = Decimal(self._take_profit_perc.Value) / Decimal(100)
stop = Decimal(self._stop_loss_perc.Value) / Decimal(100)
one = Decimal(1)
if is_long:
exit_now = (take > 0 and candle.HighPrice >= self._entry_price * (one + take)) or \
(stop > 0 and candle.LowPrice <= self._entry_price * (one - stop))
else:
exit_now = (take > 0 and candle.LowPrice <= self._entry_price * (one - take)) or \
(stop > 0 and candle.HighPrice >= self._entry_price * (one + stop))
if exit_now:
if is_long:
self.SellMarket(self.Position)
else:
self.BuyMarket(-self.Position)
return
level = Decimal(self._rsi_diff_level.Value)
long_signal = prev >= level and diff < level
short_signal = prev <= level and diff > level
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._entry_price = close
self._entry_time = candle.CloseTime
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._entry_price = close
self._entry_time = candle.CloseTime
def CreateClone(self):
return dual_rsi_differential_strategy()