Keltner Rsi 戦略
Keltner Channels と RSI インジケーターを組み合わせた戦略。価格がチャネル境界に触れ、RSI が売られすぎ/買われすぎ条件を確認した際の平均回帰の機会を探します。
テストでは年平均収益率は約 88% を示しています。株式市場で最もパフォーマンスが優れています。
Keltner Channels は最近のボラティリティをマッピングし、RSI はモメンタムの極値を測定します。RSI がチャネルを超えた動きを支持するときにエントリーが発生します。
ボラティリティエンベロープ周辺のバウンストレーダーに最適です。ATR乗数はチャネル幅を決め、ストップはエントリー価格の一定割合です。
詳細
- エントリー条件:
- ロング:
Close < LowerBand && RSI < RsiOversoldLevel - ショート:
Close > UpperBand && RSI > RsiOverboughtLevel
- ロング:
- ロング/ショート: 両方
- エグジット条件:
- 価格が EMA に戻る
- ストップ:
StopLossPercentを使用したパーセントベース - デフォルト値:
EmaPeriod= 20AtrPeriod= 14AtrMultiplier= 2.0mRsiPeriod= 14RsiOverboughtLevel= 70mRsiOversoldLevel= 30mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() チャネルはEMAの上下にAtrMultiplier倍のATRを加えたものです。ロングは終値がEMA以上、ショートはEMA以下に戻ったら決済します。 保有ポジションと逆方向のエントリーシグナルはドテンになります。
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: Keltner 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>
/// Keltner RSI strategy.
/// The Keltner Channel is the EmaPeriod EMA plus and minus AtrMultiplier times the AtrPeriod ATR. A close below the lower band with RSI
/// below RsiOversoldLevel goes long and a close above the upper band with RSI above RsiOverboughtLevel goes short, reversing an opposite
/// position. The position closes once price returns to the EMA, and a percent stop limits the loss.
/// </summary>
public class KeltnerRsiStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
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;
/// <summary>
/// Period of the channel EMA.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Period of the channel ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the channel width.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Period of RSI.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI level for shorts.
/// </summary>
public decimal RsiOverboughtLevel
{
get => _rsiOverboughtLevel.Value;
set => _rsiOverboughtLevel.Value = value;
}
/// <summary>
/// RSI level for longs.
/// </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 KeltnerRsiStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period of the channel EMA", "Keltner");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the channel ATR", "Keltner");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiplier of the channel width", "Keltner");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Period of RSI", "RSI");
_rsiOverboughtLevel = Param(nameof(RsiOverboughtLevel), 70m)
.SetDisplay("RSI Overbought", "RSI level for shorts", "RSI");
_rsiOversoldLevel = Param(nameof(RsiOversoldLevel), 30m)
.SetDisplay("RSI Oversold", "RSI level for longs", "RSI");
_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 OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, atr, 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, ema);
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 emaValue, IIndicatorValue atrValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!emaValue.IsFormed || !atrValue.IsFormed || !rsiValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var middle = emaValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var upper = middle + AtrMultiplier * atr;
var lower = middle - AtrMultiplier * atr;
var close = candle.ClosePrice;
if (close < lower && rsi < RsiOversoldLevel && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close > upper && rsi > RsiOverboughtLevel && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && close >= middle)
SellMarket(Position);
else if (Position < 0 && close <= middle)
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 ExponentialMovingAverage, AverageTrueRange, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class keltner_rsi_strategy(Strategy):
"""
Keltner RSI strategy.
The Keltner Channel is the EmaPeriod EMA plus and minus AtrMultiplier times the AtrPeriod ATR. A close below the lower band with RSI
below RsiOversoldLevel goes long and a close above the upper band with RSI above RsiOverboughtLevel goes short, reversing an opposite
position. The position closes once price returns to the EMA, and a percent stop limits the loss.
"""
def __init__(self):
super(keltner_rsi_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "Period of the channel EMA", "Keltner")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the channel ATR", "Keltner")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiplier of the channel width", "Keltner")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "Period of RSI", "RSI")
self._rsi_overbought_level = self.Param("RsiOverboughtLevel", 70.0).SetDisplay("RSI Overbought", "RSI level for shorts", "RSI")
self._rsi_oversold_level = self.Param("RsiOversoldLevel", 30.0).SetDisplay("RSI Oversold", "RSI level for longs", "RSI")
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")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(keltner_rsi_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, atr, 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, ema)
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, ema_value, atr_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if not ema_value.IsFormed or not atr_value.IsFormed or not rsi_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
middle = ema_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
rsi = rsi_value.GetValue[Decimal](None)
multiplier = Decimal(self._atr_multiplier.Value)
upper = middle + multiplier * atr
lower = middle - multiplier * atr
close = candle.ClosePrice
if close < lower and rsi < Decimal(self._rsi_oversold_level.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close > upper and rsi > Decimal(self._rsi_overbought_level.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and close >= middle:
self.SellMarket(self.Position)
elif self.Position < 0 and close <= middle:
self.BuyMarket(-self.Position)
def CreateClone(self):
return keltner_rsi_strategy()