Arsi Vwap Atr Strategy
Adaptive RSI strategy where overbought and oversold levels expand or contract based on ATR or the deviation from VWAP. Positions are opened on RSI crossovers of the adaptive levels and closed when RSI returns to the mid-zone.
Details
- Entry Criteria:
- Long:
RSIcrosses above adaptive oversold line - Short:
RSIcrosses below adaptive overbought line
- Long:
- Long/Short: Both
- Exit Criteria:
- RSI crosses back through 50 or opposite adaptive line
- Stops: Percent-based using
StopLossPercentandRiskReward - Default Values:
RsiLength= 14BaseK= 1mRiskPercent= 2mStopLossPercent= 2.5mRiskReward= 2mSourceOb= ATRSourceOs= ATRAtrLengthOb= 14AtrLengthOs= 14ObMultiplier= 10mOsMultiplier= 10mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Momentum
- Direction: Both
- Indicators: RSI, ATR, VWAP
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Adaptive RSI strategy with dynamic OB/OS levels.
/// Uses RSI crossover of overbought/oversold thresholds with EMA trend filter.
/// </summary>
public class ArsiVwapAtrStrategy : Strategy
{
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _obLevel;
private readonly StrategyParam<decimal> _osLevel;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevRsi;
private int _barIndex;
private int _lastTradeBar;
/// <summary>
/// RSI length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// EMA trend filter length.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal ObLevel
{
get => _obLevel.Value;
set => _obLevel.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal OsLevel
{
get => _osLevel.Value;
set => _osLevel.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </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>
/// Constructor.
/// </summary>
public ArsiVwapAtrStrategy()
{
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI calculation period", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "EMA trend filter period", "Indicators");
_obLevel = Param(nameof(ObLevel), 55m)
.SetDisplay("OB Level", "Overbought RSI level", "Indicators");
_osLevel = Param(nameof(OsLevel), 45m)
.SetDisplay("OS Level", "Oversold RSI level", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 300)
.SetDisplay("Cooldown Bars", "Bars between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = 0;
_barIndex = 0;
_lastTradeBar = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rsi, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
_barIndex++;
var cooldownOk = _barIndex - _lastTradeBar > CooldownBars;
// RSI crosses above OS level from below = buy signal
var longSignal = _prevRsi > 0 && _prevRsi < OsLevel && rsiValue >= OsLevel;
// RSI crosses below OB level from above = sell signal
var shortSignal = _prevRsi > 0 && _prevRsi > ObLevel && rsiValue <= ObLevel;
if (longSignal && Position <= 0 && cooldownOk)
{
BuyMarket();
_lastTradeBar = _barIndex;
}
else if (shortSignal && Position >= 0 && cooldownOk)
{
SellMarket();
_lastTradeBar = _barIndex;
}
_prevRsi = rsiValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class arsi_vwap_atr_strategy(Strategy):
"""
Adaptive RSI strategy with dynamic OB/OS levels.
Uses RSI crossover of overbought/oversold thresholds with EMA trend filter.
"""
def __init__(self):
super(arsi_vwap_atr_strategy, self).__init__()
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI calculation period", "Indicators")
self._ema_length = self.Param("EmaLength", 50) \
.SetGreaterThanZero() \
.SetDisplay("EMA Length", "EMA trend filter period", "Indicators")
self._ob_level = self.Param("ObLevel", 55.0) \
.SetDisplay("OB Level", "Overbought RSI level", "Indicators")
self._os_level = self.Param("OsLevel", 45.0) \
.SetDisplay("OS Level", "Oversold RSI level", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 300) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Trading")
self._candle_type = self.Param("CandleType", tf(1)) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_rsi = 0.0
self._bar_index = 0
self._last_trade_bar = 0
@property
def RsiLength(self): return self._rsi_length.Value
@RsiLength.setter
def RsiLength(self, v): self._rsi_length.Value = v
@property
def EmaLength(self): return self._ema_length.Value
@EmaLength.setter
def EmaLength(self, v): self._ema_length.Value = v
@property
def ObLevel(self): return self._ob_level.Value
@ObLevel.setter
def ObLevel(self, v): self._ob_level.Value = v
@property
def OsLevel(self): return self._os_level.Value
@OsLevel.setter
def OsLevel(self, v): self._os_level.Value = v
@property
def CooldownBars(self): return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, v): self._cooldown_bars.Value = v
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, v): self._candle_type.Value = v
def OnReseted(self):
super(arsi_vwap_atr_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._bar_index = 0
self._last_trade_bar = 0
def OnStarted2(self, time):
super(arsi_vwap_atr_strategy, self).OnStarted2(time)
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ema, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
self._bar_index += 1
cooldown_ok = self._bar_index - self._last_trade_bar > self.CooldownBars
long_signal = self._prev_rsi > 0 and self._prev_rsi < self.OsLevel and rsi_value >= self.OsLevel
short_signal = self._prev_rsi > 0 and self._prev_rsi > self.ObLevel and rsi_value <= self.ObLevel
if long_signal and self.Position <= 0 and cooldown_ok:
self.BuyMarket()
self._last_trade_bar = self._bar_index
elif short_signal and self.Position >= 0 and cooldown_ok:
self.SellMarket()
self._last_trade_bar = self._bar_index
self._prev_rsi = rsi_value
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return arsi_vwap_atr_strategy()