Adaptive RSI Volume Filter
Adaptive RSI Volume Filter 策略基于 that trades based on Adaptive RSI with volume confirmation。
测试表明年均收益约为 106%,该策略在股票市场表现最佳。
当 its indicators confirms filtered entries 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 MinRsiPeriod, MaxRsiPeriod 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
MinRsiPeriod = 10MaxRsiPeriod = 20AtrPeriod = 14VolumeLookback = 20CandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: multiple indicators
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that trades an ATR-adaptive RSI confirmed by relative volume.
/// </summary>
public class AdaptiveRsiVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _minRsiPeriod;
private readonly StrategyParam<int> _maxRsiPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _volumeLookback;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _fastRsi = null!;
private RelativeStrengthIndex _slowRsi = null!;
private AverageTrueRange _atr = null!;
private SimpleMovingAverage _volumeSma = null!;
private decimal _adaptiveRsiValue;
private decimal _avgVolume;
private decimal _atrValue;
private int _cooldownRemaining;
/// <summary>
/// Strategy parameter: Minimum RSI period.
/// </summary>
public int MinRsiPeriod
{
get => _minRsiPeriod.Value;
set => _minRsiPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Maximum RSI period.
/// </summary>
public int MaxRsiPeriod
{
get => _maxRsiPeriod.Value;
set => _maxRsiPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: ATR period for volatility normalization.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Strategy parameter: Volume lookback period.
/// </summary>
public int VolumeLookback
{
get => _volumeLookback.Value;
set => _volumeLookback.Value = value;
}
/// <summary>
/// Strategy parameter: Closed candles to wait between position changes.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy parameter: Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdaptiveRsiVolumeStrategy()
{
_minRsiPeriod = Param(nameof(MinRsiPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Min RSI Period", "Fast RSI period used in high volatility", "Indicator Settings");
_maxRsiPeriod = Param(nameof(MaxRsiPeriod), 21)
.SetGreaterThanZero()
.SetDisplay("Max RSI Period", "Slow RSI period used in low volatility", "Indicator Settings");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR volatility calculation", "Indicator Settings");
_volumeLookback = Param(nameof(VolumeLookback), 12)
.SetGreaterThanZero()
.SetDisplay("Volume Lookback", "Periods used for average volume", "Volume Settings");
_cooldownBars = Param(nameof(CooldownBars), 8)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Closed candles to wait before another signal", "Trading");
_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();
_adaptiveRsiValue = 50m;
_avgVolume = 0m;
_atrValue = 0m;
_cooldownRemaining = 0;
_fastRsi?.Reset();
_slowRsi?.Reset();
_atr?.Reset();
_volumeSma?.Reset();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastRsi = new RelativeStrengthIndex
{
Length = MinRsiPeriod
};
_slowRsi = new RelativeStrengthIndex
{
Length = MaxRsiPeriod
};
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
_volumeSma = new SimpleMovingAverage
{
Length = VolumeLookback
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastRsi);
DrawIndicator(area, _slowRsi);
DrawOwnTrades(area);
}
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var fastRsiValue = _fastRsi.Process(new DecimalIndicatorValue(_fastRsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var slowRsiValue = _slowRsi.Process(new DecimalIndicatorValue(_slowRsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
var atrValue = _atr.Process(new CandleIndicatorValue(_atr, candle) { IsFinal = true });
var volumeValue = _volumeSma.Process(new DecimalIndicatorValue(_volumeSma, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
if (!_fastRsi.IsFormed || !_slowRsi.IsFormed || !_atr.IsFormed || !_volumeSma.IsFormed ||
fastRsiValue.IsEmpty || slowRsiValue.IsEmpty || atrValue.IsEmpty || volumeValue.IsEmpty)
return;
_avgVolume = volumeValue.ToDecimal();
_atrValue = atrValue.ToDecimal();
var fastRsi = fastRsiValue.ToDecimal();
var slowRsi = slowRsiValue.ToDecimal();
var normalizedAtr = Math.Min(Math.Max(_atrValue / Math.Max(candle.ClosePrice * 0.02m, 1m), 0m), 1m);
// High volatility shifts weight to the faster RSI.
_adaptiveRsiValue = slowRsi + ((fastRsi - slowRsi) * normalizedAtr);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var isHighVolume = candle.TotalVolume >= (_avgVolume * 0.9m);
var oversoldLevel = 45m - (normalizedAtr * 5m);
var overboughtLevel = 55m + (normalizedAtr * 5m);
if (_cooldownRemaining == 0 && isHighVolume && _adaptiveRsiValue <= oversoldLevel && Position <= 0)
{
BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
else if (_cooldownRemaining == 0 && isHighVolume && _adaptiveRsiValue >= overboughtLevel && Position >= 0)
{
SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
_cooldownRemaining = CooldownBars;
}
else if (Position > 0 && _adaptiveRsiValue >= 52m)
{
SellMarket(Position);
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && _adaptiveRsiValue <= 48m)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = 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
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adaptive_rsi_volume_strategy(Strategy):
"""
Strategy that trades an ATR-adaptive RSI confirmed by relative volume.
"""
def __init__(self):
super(adaptive_rsi_volume_strategy, self).__init__()
self._min_rsi_period = self.Param("MinRsiPeriod", 8) \
.SetGreaterThanZero() \
.SetDisplay("Min RSI Period", "Fast RSI period used in high volatility", "Indicator Settings")
self._max_rsi_period = self.Param("MaxRsiPeriod", 21) \
.SetGreaterThanZero() \
.SetDisplay("Max RSI Period", "Slow RSI period used in low volatility", "Indicator Settings")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period for ATR volatility calculation", "Indicator Settings")
self._volume_lookback = self.Param("VolumeLookback", 12) \
.SetGreaterThanZero() \
.SetDisplay("Volume Lookback", "Periods used for average volume", "Volume Settings")
self._cooldown_bars = self.Param("CooldownBars", 8) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another signal", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_rsi = None
self._slow_rsi = None
self._atr = None
self._volume_sma = None
self._adaptive_rsi_value = 50.0
self._avg_volume = 0.0
self._atr_value = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(adaptive_rsi_volume_strategy, self).OnReseted()
self._adaptive_rsi_value = 50.0
self._avg_volume = 0.0
self._atr_value = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adaptive_rsi_volume_strategy, self).OnStarted2(time)
self._fast_rsi = RelativeStrengthIndex()
self._fast_rsi.Length = int(self._min_rsi_period.Value)
self._slow_rsi = RelativeStrengthIndex()
self._slow_rsi.Length = int(self._max_rsi_period.Value)
self._atr = AverageTrueRange()
self._atr.Length = int(self._atr_period.Value)
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = int(self._volume_lookback.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_rsi)
self.DrawIndicator(area, self._slow_rsi)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
fast_rsi_val = process_float(self._fast_rsi, candle.ClosePrice, candle.OpenTime, True)
slow_rsi_val = process_float(self._slow_rsi, candle.ClosePrice, candle.OpenTime, True)
aiv = CandleIndicatorValue(self._atr, candle)
aiv.IsFinal = True
atr_val = self._atr.Process(aiv)
volume_val = process_float(self._volume_sma, candle.TotalVolume, candle.OpenTime, True)
if not self._fast_rsi.IsFormed or not self._slow_rsi.IsFormed or not self._atr.IsFormed or not self._volume_sma.IsFormed:
return
if fast_rsi_val.IsEmpty or slow_rsi_val.IsEmpty or atr_val.IsEmpty or volume_val.IsEmpty:
return
self._avg_volume = float(volume_val)
self._atr_value = float(atr_val)
fast_rsi = float(fast_rsi_val)
slow_rsi = float(slow_rsi_val)
close_price = float(candle.ClosePrice)
normalized_atr = min(max(self._atr_value / max(close_price * 0.02, 1.0), 0.0), 1.0)
self._adaptive_rsi_value = slow_rsi + ((fast_rsi - slow_rsi) * normalized_atr)
if not self.IsFormedAndOnlineAndAllowTrading():
return
total_volume = float(candle.TotalVolume)
is_high_volume = total_volume >= (self._avg_volume * 0.9)
oversold_level = 45.0 - (normalized_atr * 5.0)
overbought_level = 55.0 + (normalized_atr * 5.0)
cooldown = int(self._cooldown_bars.Value)
if self._cooldown_remaining == 0 and is_high_volume and self._adaptive_rsi_value <= oversold_level and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and is_high_volume and self._adaptive_rsi_value >= overbought_level and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = cooldown
elif self.Position > 0 and self._adaptive_rsi_value >= 52.0:
self.SellMarket(self.Position)
self._cooldown_remaining = cooldown
elif self.Position < 0 and self._adaptive_rsi_value <= 48.0:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
def CreateClone(self):
return adaptive_rsi_volume_strategy()