YinYang RSI Volume Trend Strategy
YinYang RSI Volume Trend Strategy uses volume-weighted price zones and an RSI filter to detect trend reversals. The strategy buys when price exits the lower zone and sells when it exits the upper zone. Optional stop-loss and take-profit levels are based on dynamic zones.
Details
- Entry Criteria: Price crosses out of the calculated purchase zones with availability reset options.
- Long/Short: Both directions.
- Exit Criteria: Price reaches the opposite zone or triggers optional stop-loss/take-profit.
- Stops: Optional.
- Default Values:
TrendLength= 80UseTakeProfit= trueUseStopLoss= trueStopLossMultiplier= 0.1
- Filters:
- Category: Trend
- Direction: Both
- Indicators: VWMA, EMA, RSI
- Stops: Optional
- Complexity: Intermediate
- Timeframe: Any
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// YinYang RSI Volume Trend strategy.
/// Defines dynamic buy/sell zones using SMA, StdDev, and RSI.
/// Enters long when price crosses above lower zone, short when below upper zone.
/// </summary>
public class YinYangRsiVolumeTrendStrategy : Strategy
{
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<decimal> _stopLossMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevClose;
private decimal _prevZoneHigh;
private decimal _prevZoneLow;
private decimal _prevZoneBasis;
private bool _initialized;
public int TrendLength { get => _trendLength.Value; set => _trendLength.Value = value; }
public decimal StopLossMultiplier { get => _stopLossMultiplier.Value; set => _stopLossMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public YinYangRsiVolumeTrendStrategy()
{
_trendLength = Param(nameof(TrendLength), 40)
.SetGreaterThanZero()
.SetDisplay("Trend Length", "Lookback length", "General");
_stopLossMultiplier = Param(nameof(StopLossMultiplier), 0.5m)
.SetGreaterThanZero()
.SetDisplay("SL Mult %", "Stop distance percent", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevZoneHigh = 0;
_prevZoneLow = 0;
_prevZoneBasis = 0;
_initialized = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = TrendLength };
var stdDev = new StandardDeviation { Length = TrendLength };
var rsi = new RelativeStrengthIndex { Length = 14 };
_prevClose = 0;
_prevZoneHigh = 0;
_prevZoneLow = 0;
_prevZoneBasis = 0;
_initialized = false;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, stdDev, rsi, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaVal, decimal stdVal, decimal rsiVal)
{
if (candle.State != CandleStates.Finished)
return;
if (stdVal <= 0)
return;
// Dynamic zones based on SMA +/- RSI-weighted StdDev
var rsiWeight = rsiVal / 100m; // 0..1
var zoneWidth = stdVal * (0.5m + rsiWeight);
var zoneBasis = smaVal;
var zoneHigh = zoneBasis + zoneWidth;
var zoneLow = zoneBasis - zoneWidth;
var stopHigh = zoneHigh * (1 + StopLossMultiplier / 100m);
var stopLow = zoneLow * (1 - StopLossMultiplier / 100m);
var close = candle.ClosePrice;
if (!_initialized)
{
_prevClose = close;
_prevZoneHigh = zoneHigh;
_prevZoneLow = zoneLow;
_prevZoneBasis = zoneBasis;
_initialized = true;
return;
}
// Cross detections
var longStart = _prevClose <= _prevZoneLow && close > zoneLow;
var longEnd = _prevClose <= _prevZoneHigh && close > zoneHigh;
var longStopLoss = _prevClose >= _prevZoneLow && close < stopLow;
var shortStart = _prevClose >= _prevZoneHigh && close < zoneHigh;
var shortEnd = _prevClose >= _prevZoneLow && close < zoneLow;
var shortStopLoss = _prevClose <= _prevZoneHigh && close > stopHigh;
// Long entry: price crosses up from below lower zone
if (longStart && Position <= 0)
{
BuyMarket();
}
else if (Position > 0 && (longEnd || longStopLoss))
{
SellMarket();
}
// Short entry: price crosses down from above upper zone
if (shortStart && Position >= 0)
{
SellMarket();
}
else if (Position < 0 && (shortEnd || shortStopLoss))
{
BuyMarket();
}
_prevClose = close;
_prevZoneHigh = zoneHigh;
_prevZoneLow = zoneLow;
_prevZoneBasis = zoneBasis;
}
}
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 DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class yin_yang_rsi_volume_trend_strategy(Strategy):
def __init__(self):
super(yin_yang_rsi_volume_trend_strategy, self).__init__()
self._trend_length = self.Param("TrendLength", 40) \
.SetDisplay("Trend Length", "Lookback length", "General")
self._stop_loss_multiplier = self.Param("StopLossMultiplier", 0.5) \
.SetDisplay("SL Mult %", "Stop distance percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_zone_high = 0.0
self._prev_zone_low = 0.0
self._prev_zone_basis = 0.0
self._initialized = False
@property
def trend_length(self):
return self._trend_length.Value
@property
def stop_loss_multiplier(self):
return self._stop_loss_multiplier.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(yin_yang_rsi_volume_trend_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_zone_high = 0.0
self._prev_zone_low = 0.0
self._prev_zone_basis = 0.0
self._initialized = False
def OnStarted2(self, time):
super(yin_yang_rsi_volume_trend_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self.trend_length
std_dev = StandardDeviation()
std_dev.Length = self.trend_length
rsi = RelativeStrengthIndex()
rsi.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, std_dev, rsi, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def on_process(self, candle, sma_val, std_val, rsi_val):
if candle.State != CandleStates.Finished:
return
if std_val <= 0:
return
# Dynamic zones based on SMA +/- RSI-weighted StdDev
rsi_weight = rsi_val / 100 # 0..1
zone_width = std_val * (0.5 + rsi_weight)
zone_basis = sma_val
zone_high = zone_basis + zone_width
zone_low = zone_basis - zone_width
stop_high = zone_high * (1 + self.stop_loss_multiplier / 100)
stop_low = zone_low * (1 - self.stop_loss_multiplier / 100)
close = candle.ClosePrice
if not self._initialized:
self._prev_close = close
self._prev_zone_high = zone_high
self._prev_zone_low = zone_low
self._prev_zone_basis = zone_basis
self._initialized = True
return
# Cross detections
long_start = self._prev_close <= self._prev_zone_low and close > zone_low
long_end = self._prev_close <= self._prev_zone_high and close > zone_high
long_stop_loss = self._prev_close >= self._prev_zone_low and close < stop_low
short_start = self._prev_close >= self._prev_zone_high and close < zone_high
short_end = self._prev_close >= self._prev_zone_low and close < zone_low
short_stop_loss = self._prev_close <= self._prev_zone_high and close > stop_high
# Long entry: price crosses up from below lower zone
if long_start and self.Position <= 0:
self.BuyMarket()
elif self.Position > 0 and (long_end or long_stop_loss):
self.SellMarket()
# Short entry: price crosses down from above upper zone
if short_start and self.Position >= 0:
self.SellMarket()
elif self.Position < 0 and (short_end or short_stop_loss):
self.BuyMarket()
self._prev_close = close
self._prev_zone_high = zone_high
self._prev_zone_low = zone_low
self._prev_zone_basis = zone_basis
def CreateClone(self):
return yin_yang_rsi_volume_trend_strategy()