CVD Divergence Volume HMA RSI MACD Strategy
This strategy combines Hull Moving Averages, RSI, MACD, volume filter, and cumulative volume delta (CVD) divergence to identify trend opportunities.
Long positions open when HMA20 is above HMA50, RSI shows bullish momentum, MACD histogram rises, volume exceeds its average, and CVD forms bullish divergence or increases. Short positions mirror these conditions.
Details
- Entry Criteria:
- Long: HMA20 > HMA50 & price > HMA20; RSI between 40 and
RsiOverbought; MACD line above signal & histogram rising; volume > SMA *VolumeMultiplier; bullish CVD divergence or increasing CVD. - Short: HMA20 < HMA50 & price < HMA20; RSI between
RsiOversoldand 60; MACD line below signal & histogram falling; volume > SMA *VolumeMultiplier; bearish CVD divergence or decreasing CVD.
- Long: HMA20 > HMA50 & price > HMA20; RSI between 40 and
- Long/Short: Both sides.
- Exit Criteria:
- Long: Price < HMA20 or RSI >
RsiOverboughtor MACD line crosses below signal. - Short: Price > HMA20 or RSI <
RsiOversoldor MACD line crosses above signal.
- Long: Price < HMA20 or RSI >
- Stops: No.
- Default Values:
Hma20Length= 20Hma50Length= 50RsiLength= 14RsiOverbought= 70RsiOversold= 30MacdFast= 12MacdSlow= 26MacdSignal= 9VolumeMaLength= 20VolumeMultiplier= 1.5CvdLength= 14DivergenceLookback= 5CandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Mixed
- Direction: Both
- Indicators: HMA, RSI, MACD, Volume, CVD
- Stops: No
- Complexity: Advanced
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: Yes
- 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>
/// CVD divergence strategy with HMA trend, RSI, MACD and volume filters.
/// Each candle's volume delta is its volume signed by the candle direction; the running sum is the CVD.
/// A long needs HMA20 above HMA50 with price above HMA20, RSI between 40 and RsiOverbought, MACD above its signal with a rising histogram,
/// volume above VolumeMultiplier times its average, and a bullish CVD divergence or CVD above its CvdLength average. Shorts mirror this.
/// A long exits when price drops below HMA20, RSI rises above RsiOverbought or MACD crosses below its signal; shorts mirror this.
/// </summary>
public class CvdDivergenceVolumeHmaRsiMacdStrategy : Strategy
{
private readonly StrategyParam<int> _hma20Length;
private readonly StrategyParam<int> _hma50Length;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _volumeMaLength;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _cvdLength;
private readonly StrategyParam<int> _divergenceLookback;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private SimpleMovingAverage _cvdSma;
private readonly List<decimal> _closes = [];
private readonly List<decimal> _cvds = [];
private decimal _cvd;
private decimal? _prevMacd;
private decimal? _prevSignal;
private decimal? _prevHistogram;
/// <summary>
/// Fast HMA length.
/// </summary>
public int Hma20Length { get => _hma20Length.Value; set => _hma20Length.Value = value; }
/// <summary>
/// Slow HMA length.
/// </summary>
public int Hma50Length { get => _hma50Length.Value; set => _hma50Length.Value = value; }
/// <summary>
/// RSI length.
/// </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>
/// MACD fast period.
/// </summary>
public int MacdFast { get => _macdFast.Value; set => _macdFast.Value = value; }
/// <summary>
/// MACD slow period.
/// </summary>
public int MacdSlow { get => _macdSlow.Value; set => _macdSlow.Value = value; }
/// <summary>
/// MACD signal period.
/// </summary>
public int MacdSignal { get => _macdSignal.Value; set => _macdSignal.Value = value; }
/// <summary>
/// Volume average length.
/// </summary>
public int VolumeMaLength { get => _volumeMaLength.Value; set => _volumeMaLength.Value = value; }
/// <summary>
/// Volume must exceed its average times this multiplier.
/// </summary>
public decimal VolumeMultiplier { get => _volumeMultiplier.Value; set => _volumeMultiplier.Value = value; }
/// <summary>
/// Length of the CVD average that defines a rising or falling CVD.
/// </summary>
public int CvdLength { get => _cvdLength.Value; set => _cvdLength.Value = value; }
/// <summary>
/// Bars between the two points compared for a divergence.
/// </summary>
public int DivergenceLookback { get => _divergenceLookback.Value; set => _divergenceLookback.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public CvdDivergenceVolumeHmaRsiMacdStrategy()
{
_hma20Length = Param(nameof(Hma20Length), 20)
.SetGreaterThanZero()
.SetDisplay("HMA20 Length", "Fast HMA length", "Trend");
_hma50Length = Param(nameof(Hma50Length), 50)
.SetGreaterThanZero()
.SetDisplay("HMA50 Length", "Slow HMA length", "Trend");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI length", "RSI");
_rsiOverbought = Param(nameof(RsiOverbought), 70m)
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI");
_rsiOversold = Param(nameof(RsiOversold), 30m)
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI");
_macdFast = Param(nameof(MacdFast), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast period", "MACD");
_macdSlow = Param(nameof(MacdSlow), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow period", "MACD");
_macdSignal = Param(nameof(MacdSignal), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "MACD signal period", "MACD");
_volumeMaLength = Param(nameof(VolumeMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume MA Length", "Volume average length", "Volume");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Volume Multiplier", "Volume must exceed its average times this", "Volume");
_cvdLength = Param(nameof(CvdLength), 14)
.SetGreaterThanZero()
.SetDisplay("CVD Length", "Length of the CVD average", "CVD");
_divergenceLookback = Param(nameof(DivergenceLookback), 5)
.SetGreaterThanZero()
.SetDisplay("Divergence Lookback", "Bars between the compared divergence points", "CVD");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
ResetState();
}
private void ResetState()
{
_closes.Clear();
_cvds.Clear();
_cvd = 0m;
_prevMacd = null;
_prevSignal = null;
_prevHistogram = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var hma20 = new HullMovingAverage { Length = Hma20Length };
var hma50 = new HullMovingAverage { Length = Hma50Length };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal },
};
_volumeSma = new SimpleMovingAverage { Length = VolumeMaLength };
_cvdSma = new SimpleMovingAverage { Length = CvdLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(hma20, hma50, rsi, macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hma20);
DrawIndicator(area, hma50);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, rsi);
DrawIndicator(oscillators, macd);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hma20Value, IIndicatorValue hma50Value, IIndicatorValue rsiValue, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
var delta = close > candle.OpenPrice ? volume : close < candle.OpenPrice ? -volume : 0m;
_cvd += delta;
var volumeAvg = _volumeSma.Process(volume, candle.ServerTime, true);
var cvdAvg = _cvdSma.Process(_cvd, candle.ServerTime, true);
_closes.Add(close);
_cvds.Add(_cvd);
var maxHistory = DivergenceLookback + 1;
if (_closes.Count > maxHistory)
{
_closes.RemoveAt(0);
_cvds.RemoveAt(0);
}
if (macdValue is not IMovingAverageConvergenceDivergenceSignalValue { Macd: decimal macdLine, Signal: decimal signalLine })
return;
var histogram = macdLine - signalLine;
var prevMacd = _prevMacd;
var prevSignal = _prevSignal;
var prevHistogram = _prevHistogram;
_prevMacd = macdLine;
_prevSignal = signalLine;
_prevHistogram = histogram;
if (!hma20Value.IsFormed || !hma50Value.IsFormed || !rsiValue.IsFormed || !volumeAvg.IsFormed || !cvdAvg.IsFormed)
return;
if (prevMacd is not decimal pm || prevSignal is not decimal ps || prevHistogram is not decimal ph || _closes.Count < maxHistory)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var hma20 = hma20Value.GetValue<decimal>();
var hma50 = hma50Value.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var cvdMa = cvdAvg.GetValue<decimal>();
var macdCrossDown = pm >= ps && macdLine < signalLine;
var macdCrossUp = pm <= ps && macdLine > signalLine;
if (Position > 0)
{
if (close < hma20 || rsi > RsiOverbought || macdCrossDown)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (close > hma20 || rsi < RsiOversold || macdCrossUp)
BuyMarket(-Position);
return;
}
var oldClose = _closes[0];
var oldCvd = _cvds[0];
// Price makes a lower point while CVD makes a higher one, or the reverse.
var bullishDivergence = close < oldClose && _cvd > oldCvd;
var bearishDivergence = close > oldClose && _cvd < oldCvd;
var volumeOk = volume > volumeAvg.GetValue<decimal>() * VolumeMultiplier;
var longSignal = hma20 > hma50 && close > hma20
&& rsi > 40m && rsi < RsiOverbought
&& macdLine > signalLine && histogram > ph
&& volumeOk
&& (bullishDivergence || _cvd > cvdMa);
var shortSignal = hma20 < hma50 && close < hma20
&& rsi > RsiOversold && rsi < 60m
&& macdLine < signalLine && histogram < ph
&& volumeOk
&& (bearishDivergence || _cvd < cvdMa);
if (longSignal)
BuyMarket();
else if (shortSignal)
SellMarket();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import HullMovingAverage, RelativeStrengthIndex, MovingAverageConvergenceDivergenceSignal, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class cvd_divergence_volume_hma_rsi_macd_strategy(Strategy):
"""
CVD divergence strategy with HMA trend, RSI, MACD and volume filters.
Each candle's volume delta is its volume signed by the candle direction; the running sum is the CVD.
A long needs HMA20 above HMA50 with price above HMA20, RSI between 40 and RsiOverbought, MACD above its signal with a rising histogram,
volume above VolumeMultiplier times its average, and a bullish CVD divergence or CVD above its CvdLength average. Shorts mirror this.
A long exits when price drops below HMA20, RSI rises above RsiOverbought or MACD crosses below its signal; shorts mirror this.
"""
def __init__(self):
super(cvd_divergence_volume_hma_rsi_macd_strategy, self).__init__()
self._hma20_length = self.Param("Hma20Length", 20).SetGreaterThanZero().SetDisplay("HMA20 Length", "Fast HMA length", "Trend")
self._hma50_length = self.Param("Hma50Length", 50).SetGreaterThanZero().SetDisplay("HMA50 Length", "Slow HMA length", "Trend")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI length", "RSI")
self._rsi_overbought = self.Param("RsiOverbought", 70.0).SetDisplay("RSI Overbought", "RSI overbought level", "RSI")
self._rsi_oversold = self.Param("RsiOversold", 30.0).SetDisplay("RSI Oversold", "RSI oversold level", "RSI")
self._macd_fast = self.Param("MacdFast", 12).SetGreaterThanZero().SetDisplay("MACD Fast", "MACD fast period", "MACD")
self._macd_slow = self.Param("MacdSlow", 26).SetGreaterThanZero().SetDisplay("MACD Slow", "MACD slow period", "MACD")
self._macd_signal = self.Param("MacdSignal", 9).SetGreaterThanZero().SetDisplay("MACD Signal", "MACD signal period", "MACD")
self._volume_ma_length = self.Param("VolumeMaLength", 20).SetGreaterThanZero().SetDisplay("Volume MA Length", "Volume average length", "Volume")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.5).SetGreaterThanZero().SetDisplay("Volume Multiplier", "Volume must exceed its average times this", "Volume")
self._cvd_length = self.Param("CvdLength", 14).SetGreaterThanZero().SetDisplay("CVD Length", "Length of the CVD average", "CVD")
self._divergence_lookback = self.Param("DivergenceLookback", 5).SetGreaterThanZero().SetDisplay("Divergence Lookback", "Bars between the compared divergence points", "CVD")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
self._cvd_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def _reset_state(self):
self._closes = []
self._cvds = []
self._cvd = 0.0
self._prev_macd = None
self._prev_signal = None
self._prev_histogram = None
def OnReseted(self):
super(cvd_divergence_volume_hma_rsi_macd_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(cvd_divergence_volume_hma_rsi_macd_strategy, self).OnStarted2(time)
self._reset_state()
hma20 = HullMovingAverage()
hma20.Length = self._hma20_length.Value
hma50 = HullMovingAverage()
hma50.Length = self._hma50_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macd_fast.Value
macd.Macd.LongMa.Length = self._macd_slow.Value
macd.SignalMa.Length = self._macd_signal.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_ma_length.Value
self._cvd_sma = SimpleMovingAverage()
self._cvd_sma.Length = self._cvd_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(hma20, hma50, rsi, macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma20)
self.DrawIndicator(area, hma50)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
self.DrawIndicator(oscillators, macd)
def _process_candle(self, candle, hma20_value, hma50_value, rsi_value, macd_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
volume = float(candle.TotalVolume)
delta = volume if close > open_price else (-volume if close < open_price else 0.0)
self._cvd += delta
volume_avg = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True)
cvd_avg = process_float(self._cvd_sma, Decimal(self._cvd), candle.ServerTime, True)
self._closes.append(close)
self._cvds.append(self._cvd)
max_history = self._divergence_lookback.Value + 1
if len(self._closes) > max_history:
self._closes.pop(0)
self._cvds.pop(0)
if macd_value.Macd is None or macd_value.Signal is None:
return
macd_line = float(macd_value.Macd)
signal_line = float(macd_value.Signal)
histogram = macd_line - signal_line
prev_macd = self._prev_macd
prev_signal = self._prev_signal
prev_histogram = self._prev_histogram
self._prev_macd = macd_line
self._prev_signal = signal_line
self._prev_histogram = histogram
if not hma20_value.IsFormed or not hma50_value.IsFormed or not rsi_value.IsFormed or not volume_avg.IsFormed or not cvd_avg.IsFormed:
return
if prev_macd is None or prev_signal is None or prev_histogram is None or len(self._closes) < max_history:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
hma20 = float(hma20_value.GetValue[Decimal](None))
hma50 = float(hma50_value.GetValue[Decimal](None))
rsi = float(rsi_value.GetValue[Decimal](None))
cvd_ma = float(cvd_avg.GetValue[Decimal](None))
overbought = float(self._rsi_overbought.Value)
oversold = float(self._rsi_oversold.Value)
macd_cross_down = prev_macd >= prev_signal and macd_line < signal_line
macd_cross_up = prev_macd <= prev_signal and macd_line > signal_line
if self.Position > 0:
if close < hma20 or rsi > overbought or macd_cross_down:
self.SellMarket(self.Position)
return
if self.Position < 0:
if close > hma20 or rsi < oversold or macd_cross_up:
self.BuyMarket(-self.Position)
return
old_close = self._closes[0]
old_cvd = self._cvds[0]
# Price makes a lower point while CVD makes a higher one, or the reverse.
bullish_divergence = close < old_close and self._cvd > old_cvd
bearish_divergence = close > old_close and self._cvd < old_cvd
volume_ok = volume > float(volume_avg.GetValue[Decimal](None)) * float(self._volume_multiplier.Value)
long_signal = (hma20 > hma50 and close > hma20
and 40.0 < rsi < overbought
and macd_line > signal_line and histogram > prev_histogram
and volume_ok
and (bullish_divergence or self._cvd > cvd_ma))
short_signal = (hma20 < hma50 and close < hma20
and oversold < rsi < 60.0
and macd_line < signal_line and histogram < prev_histogram
and volume_ok
and (bearish_divergence or self._cvd < cvd_ma))
if long_signal:
self.BuyMarket()
elif short_signal:
self.SellMarket()
def CreateClone(self):
return cvd_divergence_volume_hma_rsi_macd_strategy()