Donchian RSI 戦略
Donchian Channels と RSI インジケーターを組み合わせた戦略。RSI がトレンドが過度に延びていないことを確認したとき、Donchian のブレイクアウトで買いを行います。
テストでは年平均リターン約 55% を示しています。株式市場で最も優れたパフォーマンスを発揮します。
Donchian Channels はブレイクアウトレベルを特定し、RSI はモメンタムがその動きを支持しているかを確認します。ブレイクアウトが RSI の方向と一致したときにポジションを建てます。
フェイクアウトではなく持続的なブレイクアウトを期待するトレーダーに最適です。パーセントストップでリスクを限定します。
詳細
- エントリー条件:
- ロング:
Close > DonchianHigh && RSI < RsiOverboughtLevel - ショート:
Close < DonchianLow && RSI > RsiOversoldLevel
- ロング:
- ロング/ショート: 両方
- エグジット条件:
- ブレイクアウトの失敗または反対のシグナル
- ストップ:
StopLossPercentを使用したパーセントベース - デフォルト値:
DonchianPeriod= 20RsiPeriod= 14RsiOverboughtLevel= 70mRsiOversoldLevel= 30mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() RSIがRsiOverboughtLevel未満(ロング)またはRsiOversoldLevel超(ショート)である限り、ブレイクアウトは行き過ぎではありません。チャネルは直前DonchianPeriod本で構成します。価格が突破した水準の反対側に戻って引けたらブレイク失敗としてポジションを閉じます。 保有ポジションと逆方向のエントリーシグナルはドテンになります。
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: Donchian Channel, 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>
/// Donchian RSI strategy.
/// The channel spans the highest high and lowest low of the previous DonchianPeriod candles. A close above it while RSI is still below
/// RsiOverboughtLevel goes long and a close below it while RSI is above RsiOversoldLevel goes short, reversing an opposite position.
/// The breakout fails, closing the position, when price closes back beyond the broken level, and a percent stop limits the loss.
/// </summary>
public class DonchianRsiStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOverboughtLevel;
private readonly StrategyParam<decimal> _rsiOversoldLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevUpper;
private decimal? _prevLower;
private decimal _breakoutLevel;
/// <summary>
/// Previous candles the channel spans.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Period of RSI.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI level a long breakout must stay below.
/// </summary>
public decimal RsiOverboughtLevel
{
get => _rsiOverboughtLevel.Value;
set => _rsiOverboughtLevel.Value = value;
}
/// <summary>
/// RSI level a short breakout must stay above.
/// </summary>
public decimal RsiOversoldLevel
{
get => _rsiOversoldLevel.Value;
set => _rsiOversoldLevel.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DonchianRsiStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Previous candles the channel spans", "Indicators");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period of RSI", "Indicators");
_rsiOverboughtLevel = Param(nameof(RsiOverboughtLevel), 70m)
.SetDisplay("RSI Overbought", "RSI level a long breakout must stay below", "Indicators");
_rsiOversoldLevel = Param(nameof(RsiOversoldLevel), 30m)
.SetDisplay("RSI Oversold", "RSI level a short breakout must stay above", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_prevUpper = null;
_prevLower = null;
_breakoutLevel = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevUpper = null;
_prevLower = null;
_breakoutLevel = default;
var donchian = new DonchianChannels { Length = DonchianPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(donchian, rsi, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, rsi);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue donchianValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
// The channel is measured on the candles before this one.
var upper = _prevUpper;
var lower = _prevLower;
if (donchianValue.IsFormed && donchianValue is IDonchianChannelsValue { UpperBand: decimal currentUpper, LowerBand: decimal currentLower })
{
_prevUpper = currentUpper;
_prevLower = currentLower;
}
if (!rsiValue.IsFormed || upper is not decimal channelHigh || lower is not decimal channelLow)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var rsi = rsiValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (close > channelHigh && rsi < RsiOverboughtLevel && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_breakoutLevel = channelHigh;
}
else if (close < channelLow && rsi > RsiOversoldLevel && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_breakoutLevel = channelLow;
}
else if (Position > 0 && close < _breakoutLevel)
{
SellMarket(Position);
}
else if (Position < 0 && close > _breakoutLevel)
{
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import DonchianChannels, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class donchian_rsi_strategy(Strategy):
"""
Donchian RSI strategy.
The channel spans the highest high and lowest low of the previous DonchianPeriod candles. A close above it while RSI is still below
RsiOverboughtLevel goes long and a close below it while RSI is above RsiOversoldLevel goes short, reversing an opposite position.
The breakout fails, closing the position, when price closes back beyond the broken level, and a percent stop limits the loss.
"""
def __init__(self):
super(donchian_rsi_strategy, self).__init__()
self._donchian_period = self.Param("DonchianPeriod", 20).SetGreaterThanZero().SetDisplay("Donchian Period", "Previous candles the channel spans", "Indicators")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "Period of RSI", "Indicators")
self._rsi_overbought_level = self.Param("RsiOverboughtLevel", 70.0).SetDisplay("RSI Overbought", "RSI level a long breakout must stay below", "Indicators")
self._rsi_oversold_level = self.Param("RsiOversoldLevel", 30.0).SetDisplay("RSI Oversold", "RSI level a short breakout must stay above", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).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_upper = None
self._prev_lower = None
self._breakout_level = Decimal(0)
def OnReseted(self):
super(donchian_rsi_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(donchian_rsi_strategy, self).OnStarted2(time)
self._reset_state()
donchian = DonchianChannels()
donchian.Length = self._donchian_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(donchian, rsi, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, donchian_value, rsi_value):
if candle.State != CandleStates.Finished:
return
# The channel is measured on the candles before this one.
upper = self._prev_upper
lower = self._prev_lower
if donchian_value.IsFormed and donchian_value.UpperBand is not None and donchian_value.LowerBand is not None:
self._prev_upper = donchian_value.UpperBand
self._prev_lower = donchian_value.LowerBand
if not rsi_value.IsFormed or upper is None or lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
rsi = rsi_value.GetValue[Decimal](None)
close = candle.ClosePrice
if close > upper and rsi < Decimal(self._rsi_overbought_level.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._breakout_level = upper
elif close < lower and rsi > Decimal(self._rsi_oversold_level.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._breakout_level = lower
elif self.Position > 0 and close < self._breakout_level:
self.SellMarket(self.Position)
elif self.Position < 0 and close > self._breakout_level:
self.BuyMarket(-self.Position)
def CreateClone(self):
return donchian_rsi_strategy()