RSI + 1200戦略
RSI + 1200戦略は、相対強度と上位時間軸のトレンドフィルターによって確認された トレンドリバーサルを捉えることを目指します。クラシックな14期間の Relative Strength Indexと、 120分のマルチタイムフレームシリーズで計算された指数移動平均を組み合わせます("1200"は 元のコンセプトでの上位時間軸を指します)。モメンタムとトレンドフィルターが一致した場合にのみ 取引シグナルが取られます。
流動性の高い仮想通貨ペアでのバックテストは、この手法が持続的な方向性のある市場で最も 機能することを示しています。乱雑またはレンジバウンドの期間は偽シグナルを生成する可能性があるため、 戦略にはEMAの周囲に小さな価格スラックと、リスク管理を助けるパーセンテージベースのストップロスが含まれています。
RSIが売られすぎの領域から上向きにクロスし、価格が上位時間軸EMAの1パーセント以内の上方にある時に
ロング取引が開かれます。ショートの設定は鏡像の条件です。ポジションはRSIが反対の極値に達した時に
クローズされ、動きの枯渇を示します。エントリー価格からstopLossPercentパーセントの位置に
保護的なストップも置かれます。
詳細
- エントリー条件
- ロング: RSIが
rsiOversoldを上抜けし、終値がEMAの1%以内の上方にある。 - ショート: RSIが
rsiOverboughtを下抜けし、終値がEMAの1%以内の下方にある。
- ロング: RSIが
- エグジット条件
- ロング: RSIが
rsiOverboughtを上回る。 - ショート: RSIが
rsiOversoldを下回る。
- ロング: RSIが
- ストップ:
stopLossPercentによるオプションのパーセンテージストップ‑ロス。 - デフォルトパラメーター
rsiLength= 14rsiOverbought= 72rsiOversold= 28emaLength= 150mtfTimeframe= 120 分stopLossPercent= 0.10 (10%)
- フィルター
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: RSI, EMA
- ストップ: はい
- 複雑さ: 中
- 時間軸: イントラデイ / マルチ‑タイムフレーム
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中程度
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// RSI + 1200 Strategy.
/// An EMA of EmaLength bars is calculated on MtfTimeframe candles. A long opens when RSI crosses above RsiOversold and the close
/// is at most 1% above that EMA; a short opens when RSI crosses below RsiOverbought and the close is no more than 1% below it. Longs close once RSI rises above RsiOverbought and shorts once it falls below RsiOversold.
/// A stop at StopLossPercent (a fraction, 0.10 = 10%) from the entry limits the loss.
/// </summary>
public class RsiPlus1200Strategy : Strategy
{
private const decimal EmaSlack = 0.01m;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<DataType> _mtfTimeframe;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal? _prevRsi;
private decimal? _mtfEma;
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// EMA period on the higher time frame.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// Higher time frame of the EMA.
/// </summary>
public DataType MtfTimeframe
{
get => _mtfTimeframe.Value;
set => _mtfTimeframe.Value = value;
}
/// <summary>
/// Stop loss as a fraction of the entry price (0.10 = 10%).
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type of the RSI signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public RsiPlus1200Strategy()
{
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 72m)
.SetDisplay("RSI Overbought", "RSI overbought level", "Indicators");
_rsiOversold = Param(nameof(RsiOversold), 28m)
.SetDisplay("RSI Oversold", "RSI oversold level", "Indicators");
_emaLength = Param(nameof(EmaLength), 150)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA period on the higher time frame", "Indicators");
_mtfTimeframe = Param(nameof(MtfTimeframe), TimeSpan.FromMinutes(120).TimeFrame())
.SetDisplay("MTF Timeframe", "Higher time frame of the EMA", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 0.10m)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss as a fraction of the entry price (0.10 = 10%)", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle type of the RSI signals", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, MtfTimeframe)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = null;
_mtfEma = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
_mtfEma = null;
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
SubscribeCandles(MtfTimeframe)
.Bind(_ema, ProcessMtfCandle)
.Start();
if (StopLossPercent > 0m)
{
StartProtection(new Unit(), new Unit(StopLossPercent * 100m, 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);
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 ProcessMtfCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished || !_ema.IsFormed)
return;
_mtfEma = emaValue;
}
private void ProcessCandle(ICandleMessage candle, decimal rsi)
{
if (candle.State != CandleStates.Finished || !_rsi.IsFormed)
return;
var prevRsi = _prevRsi;
_prevRsi = rsi;
if (prevRsi is not decimal previous || _mtfEma is not decimal ema)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var crossUpOversold = previous <= RsiOversold && rsi > RsiOversold;
var crossDownOverbought = previous >= RsiOverbought && rsi < RsiOverbought;
if (crossUpOversold && close <= ema * (1m + EmaSlack) && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDownOverbought && close >= ema * (1m - EmaSlack) && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && rsi > RsiOverbought)
SellMarket(Position);
else if (Position < 0 && rsi < RsiOversold)
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 RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
EMA_SLACK = 0.01
class rsi_plus_1200_strategy(Strategy):
"""
RSI + 1200 Strategy.
An EMA of EmaLength bars is calculated on MtfTimeframe candles. A long opens when RSI crosses above RsiOversold and the close
is at most 1% above that EMA; a short opens when RSI crosses below RsiOverbought and the close is no more than 1% below it.
Longs close once RSI rises above RsiOverbought and shorts once it falls below RsiOversold.
A stop at StopLossPercent (a fraction, 0.10 = 10%) from the entry limits the loss.
"""
def __init__(self):
super(rsi_plus_1200_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 72.0).SetDisplay("RSI Overbought", "RSI overbought level", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 28.0).SetDisplay("RSI Oversold", "RSI oversold level", "Indicators")
self._ema_length = self.Param("EmaLength", 150).SetGreaterThanZero().SetDisplay("EMA Length", "EMA period on the higher time frame", "Indicators")
self._mtf_timeframe = self.Param("MtfTimeframe", DataType.TimeFrame(TimeSpan.FromMinutes(120))).SetDisplay("MTF Timeframe", "Higher time frame of the EMA", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 0.10).SetNotNegative().SetDisplay("Stop Loss", "Stop loss as a fraction of the entry price (0.10 = 10%)", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Candle type of the RSI signals", "General")
self._rsi = None
self._ema = None
self._prev_rsi = None
self._mtf_ema = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rsi_plus_1200_strategy, self).OnReseted()
self._prev_rsi = None
self._mtf_ema = None
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, self._mtf_timeframe.Value)]
def OnStarted2(self, time):
super(rsi_plus_1200_strategy, self).OnStarted2(time)
self._prev_rsi = None
self._mtf_ema = None
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self._rsi_length.Value
self._ema = ExponentialMovingAverage()
self._ema.Length = self._ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._process_candle).Start()
self.SubscribeCandles(self._mtf_timeframe.Value).Bind(self._ema, self._process_mtf_candle).Start()
stop = float(self._stop_loss_percent.Value)
if stop > 0:
self.StartProtection(Unit(), Unit(Decimal(stop * 100.0), 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.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, self._rsi)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_mtf_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished or not self._ema.IsFormed:
return
self._mtf_ema = float(ema_value)
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished or not self._rsi.IsFormed:
return
rsi = float(rsi_value)
previous = self._prev_rsi
self._prev_rsi = rsi
if previous is None or self._mtf_ema is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
ema = self._mtf_ema
close = float(candle.ClosePrice)
overbought = float(self._rsi_overbought.Value)
oversold = float(self._rsi_oversold.Value)
cross_up_oversold = previous <= oversold and rsi > oversold
cross_down_overbought = previous >= overbought and rsi < overbought
if cross_up_oversold and close <= ema * (1.0 + EMA_SLACK) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_down_overbought and close >= ema * (1.0 - EMA_SLACK) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and rsi > overbought:
self.SellMarket(self.Position)
elif self.Position < 0 and rsi < oversold:
self.BuyMarket(-self.Position)
def CreateClone(self):
return rsi_plus_1200_strategy()