RSI Dynamic Overbought Oversold
RSI Dynamic Overbought Oversold 策略基于相关指标构建。
测试表明年均收益约为 178%,该策略在股票市场表现最佳。
当指标在指定周期的数据上确认条件时触发信号,适合积极交易者。
止损依赖ATR倍数及其他参数,可根据需要调整默认值以平衡风险和收益。
详细信息
- 入场条件: see implementation for indicator conditions.
- 多空: Both directions.
- 出场条件: opposite signal or stop logic.
- 止损: Yes, using indicator-based calculations.
- 默认值:
RsiPeriod = 14MovingAvgPeriod = 50StdDevMultiplier = 2.0mStopLossPercent = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: Trend following
- 方向: Both
- 指标: Overbought, Oversold
- 止损: Yes
- 复杂度: Intermediate
- 时间框架: Intraday (5m)
- 季节性: No
- 神经网络: No
- 背离: No
- 风险级别: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// RSI strategy with dynamic overbought and oversold bands derived from the rolling mean and volatility of RSI.
/// </summary>
public class RsiDynamicOverboughtOversoldStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _movingAvgPeriod;
private readonly StrategyParam<decimal> _stdDevMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private SimpleMovingAverage _priceSma;
private SimpleMovingAverage _rsiSma;
private StandardDeviation _rsiStdDev;
private int _cooldown;
/// <summary>
/// Period for RSI calculation.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Period for moving averages and RSI volatility.
/// </summary>
public int MovingAvgPeriod
{
get => _movingAvgPeriod.Value;
set => _movingAvgPeriod.Value = value;
}
/// <summary>
/// Multiplier used for the dynamic RSI bands.
/// </summary>
public decimal StdDevMultiplier
{
get => _stdDevMultiplier.Value;
set => _stdDevMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Bars to wait after each order.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public RsiDynamicOverboughtOversoldStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetRange(2, 100)
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators");
_movingAvgPeriod = Param(nameof(MovingAvgPeriod), 34)
.SetRange(5, 200)
.SetDisplay("Average Period", "Period for moving averages and RSI volatility", "Indicators");
_stdDevMultiplier = Param(nameof(StdDevMultiplier), 1.3m)
.SetRange(0.1m, 5m)
.SetDisplay("StdDev Multiplier", "Multiplier for the dynamic RSI bands", "Signals");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetRange(0.5m, 10m)
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 48)
.SetRange(1, 500)
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_priceSma = null;
_rsiSma = null;
_rsiStdDev = null;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Security is not specified.");
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_priceSma = new SimpleMovingAverage { Length = MovingAvgPeriod };
_rsiSma = new SimpleMovingAverage { Length = MovingAvgPeriod };
_rsiStdDev = new StandardDeviation { Length = MovingAvgPeriod };
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _priceSma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _rsi);
DrawIndicator(area, _priceSma);
DrawOwnTrades(area);
}
StartProtection(new Unit(0, UnitTypes.Absolute), new Unit(StopLossPercent, UnitTypes.Percent), false);
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal priceSmaValue)
{
if (candle.State != CandleStates.Finished)
return;
var rsiAverageValue = _rsiSma.Process(rsiValue, candle.OpenTime, true).ToDecimal();
var rsiStdDevValue = _rsiStdDev.Process(rsiValue, candle.OpenTime, true).ToDecimal();
if (!_rsi.IsFormed || !_priceSma.IsFormed || !_rsiSma.IsFormed || !_rsiStdDev.IsFormed)
return;
if (ProcessState != ProcessStates.Started)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var dynamicOverbought = Math.Min(rsiAverageValue + StdDevMultiplier * rsiStdDevValue, 85m);
var dynamicOversold = Math.Max(rsiAverageValue - StdDevMultiplier * rsiStdDevValue, 15m);
var price = candle.ClosePrice;
var bullishFilter = price >= priceSmaValue * 0.995m;
var bearishFilter = price <= priceSmaValue * 1.005m;
if (Position == 0)
{
if (rsiValue <= dynamicOversold && bullishFilter)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (rsiValue >= dynamicOverbought && bearishFilter)
{
SellMarket();
_cooldown = CooldownBars;
}
return;
}
if (Position > 0 && (rsiValue >= rsiAverageValue || price < priceSmaValue * 0.995m))
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && (rsiValue <= rsiAverageValue || price > priceSmaValue * 1.005m))
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class rsi_dynamic_overbought_oversold_strategy(Strategy):
"""
RSI strategy with dynamic overbought and oversold bands derived from
the rolling mean and volatility of RSI.
"""
def __init__(self):
super(rsi_dynamic_overbought_oversold_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Period for RSI calculation", "Indicators")
self._moving_avg_period = self.Param("MovingAvgPeriod", 34) \
.SetDisplay("Average Period", "Period for moving averages and RSI volatility", "Indicators")
self._std_dev_multiplier = self.Param("StdDevMultiplier", 1.3) \
.SetDisplay("StdDev Multiplier", "Multiplier for the dynamic RSI bands", "Signals")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 48) \
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles for the strategy", "General")
self._rsi = None
self._price_sma = None
self._rsi_sma = None
self._rsi_std_dev = None
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(rsi_dynamic_overbought_oversold_strategy, self).OnReseted()
self._rsi = None
self._price_sma = None
self._rsi_sma = None
self._rsi_std_dev = None
self._cooldown = 0
def OnStarted2(self, time):
super(rsi_dynamic_overbought_oversold_strategy, self).OnStarted2(time)
ma_period = int(self._moving_avg_period.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_period.Value)
self._price_sma = SimpleMovingAverage()
self._price_sma.Length = ma_period
self._rsi_sma = SimpleMovingAverage()
self._rsi_sma.Length = ma_period
self._rsi_std_dev = StandardDeviation()
self._rsi_std_dev.Length = ma_period
self._cooldown = 0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._rsi, self._price_sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._rsi)
self.DrawIndicator(area, self._price_sma)
self.DrawOwnTrades(area)
self.StartProtection(Unit(0, UnitTypes.Absolute), Unit(self._stop_loss_percent.Value, UnitTypes.Percent), False)
def _process_candle(self, candle, rsi_value, price_sma_value):
if candle.State != CandleStates.Finished:
return
rv = float(rsi_value)
rsi_average_value = float(process_float(self._rsi_sma, Decimal(rv), candle.OpenTime, True))
rsi_std_dev_value = float(process_float(self._rsi_std_dev, Decimal(rv), candle.OpenTime, True))
if not self._rsi.IsFormed or not self._price_sma.IsFormed or not self._rsi_sma.IsFormed or not self._rsi_std_dev.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
sdm = float(self._std_dev_multiplier.Value)
dynamic_overbought = min(rsi_average_value + sdm * rsi_std_dev_value, 85.0)
dynamic_oversold = max(rsi_average_value - sdm * rsi_std_dev_value, 15.0)
price = float(candle.ClosePrice)
psv = float(price_sma_value)
bullish_filter = price >= psv * 0.995
bearish_filter = price <= psv * 1.005
cd = int(self._cooldown_bars.Value)
if self.Position == 0:
if rv <= dynamic_oversold and bullish_filter:
self.BuyMarket()
self._cooldown = cd
elif rv >= dynamic_overbought and bearish_filter:
self.SellMarket()
self._cooldown = cd
return
if self.Position > 0 and (rv >= rsi_average_value or price < psv * 0.995):
self.SellMarket(Math.Abs(self.Position))
self._cooldown = cd
elif self.Position < 0 and (rv <= rsi_average_value or price > psv * 1.005):
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = cd
def CreateClone(self):
return rsi_dynamic_overbought_oversold_strategy()