CVD-Divergenz-Strategie
Die Strategie kombiniert die Divergenz des kumulierten Volumen-Deltas (CVD) mit Hull Moving Averages, RSI, MACD und einem Volumenfilter. Ein Trade öffnet sich, wenn Trend, Momentum und Volumen übereinstimmen und das CVD Divergenz oder Fortsetzung in Handelsrichtung zeigt. Positionen schließen bei entgegengesetzten Signalen oder Indikatorkreuzungen.
Details
- Einstiegskriterien: Trendausrichtung durch HMA, RSI- und MACD-Bestätigung, hohes Volumen und CVD-Divergenz/-Fortsetzung.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: Entgegengesetztes Signal oder Indikatorkreuzung.
- Stops: Keine expliziten Stops.
- Standardwerte:
HmaFastLength= 20HmaSlowLength= 50RsiLength= 14RsiOverbought= 70RsiOversold= 30MacdFast= 12MacdSlow= 26MacdSignal= 9VolumeMaLength= 20VolumeMultiplier= 1.5mCvdLength= 14DivergenceLookback= 5CandleType= TimeSpan.FromMinutes(1)
- Filter:
- Kategorie: Divergenz
- Richtung: Beide
- Indikatoren: HMA, RSI, MACD, Volumen
- Stops: Nein
- Komplexität: Mittel
- Zeitrahmen: Intraday
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Ja
- Risikolevel: Mittel
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 the fast HMA above the slow HMA with price above the fast HMA, 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.
/// An opposite signal reverses the position. A long also exits when price drops below the fast HMA, RSI rises above RsiOverbought
/// or MACD crosses below its signal; shorts mirror this.
/// </summary>
public class CvdDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _hmaFastLength;
private readonly StrategyParam<int> _hmaSlowLength;
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 HmaFastLength { get => _hmaFastLength.Value; set => _hmaFastLength.Value = value; }
/// <summary>
/// Slow HMA length.
/// </summary>
public int HmaSlowLength { get => _hmaSlowLength.Value; set => _hmaSlowLength.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 CvdDivergenceStrategy()
{
_hmaFastLength = Param(nameof(HmaFastLength), 20)
.SetGreaterThanZero()
.SetDisplay("HMA Fast Length", "Fast HMA length", "Trend");
_hmaSlowLength = Param(nameof(HmaSlowLength), 50)
.SetGreaterThanZero()
.SetDisplay("HMA Slow 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(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();
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 hmaFast = new HullMovingAverage { Length = HmaFastLength };
var hmaSlow = new HullMovingAverage { Length = HmaSlowLength };
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(hmaFast, hmaSlow, rsi, macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, hmaFast);
DrawIndicator(area, hmaSlow);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, rsi);
DrawIndicator(oscillators, macd);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue hmaFastValue, IIndicatorValue hmaSlowValue, 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 (!hmaFastValue.IsFormed || !hmaSlowValue.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 hmaFast = hmaFastValue.GetValue<decimal>();
var hmaSlow = hmaSlowValue.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var cvdMa = cvdAvg.GetValue<decimal>();
var macdCrossDown = pm >= ps && macdLine < signalLine;
var macdCrossUp = pm <= ps && macdLine > signalLine;
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 = hmaFast > hmaSlow && close > hmaFast
&& rsi > 40m && rsi < RsiOverbought
&& macdLine > signalLine && histogram > ph
&& volumeOk
&& (bullishDivergence || _cvd > cvdMa);
var shortSignal = hmaFast < hmaSlow && close < hmaFast
&& rsi > RsiOversold && rsi < 60m
&& macdLine < signalLine && histogram < ph
&& volumeOk
&& (bearishDivergence || _cvd < cvdMa);
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && (close < hmaFast || rsi > RsiOverbought || macdCrossDown))
SellMarket(Position);
else if (Position < 0 && (close > hmaFast || rsi < RsiOversold || macdCrossUp))
BuyMarket(-Position);
}
}
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_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 the fast HMA above the slow HMA with price above the fast HMA, 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.
An opposite signal reverses the position. A long also exits when price drops below the fast HMA, RSI rises above RsiOverbought
or MACD crosses below its signal; shorts mirror this.
"""
def __init__(self):
super(cvd_divergence_strategy, self).__init__()
self._hma_fast_length = self.Param("HmaFastLength", 20).SetGreaterThanZero().SetDisplay("HMA Fast Length", "Fast HMA length", "Trend")
self._hma_slow_length = self.Param("HmaSlowLength", 50).SetGreaterThanZero().SetDisplay("HMA Slow 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(1))).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_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(cvd_divergence_strategy, self).OnStarted2(time)
self._reset_state()
hma_fast = HullMovingAverage()
hma_fast.Length = self._hma_fast_length.Value
hma_slow = HullMovingAverage()
hma_slow.Length = self._hma_slow_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(hma_fast, hma_slow, rsi, macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, hma_fast)
self.DrawIndicator(area, hma_slow)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
self.DrawIndicator(oscillators, macd)
def _process_candle(self, candle, hma_fast_value, hma_slow_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 hma_fast_value.IsFormed or not hma_slow_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
hma_fast = float(hma_fast_value.GetValue[Decimal](None))
hma_slow = float(hma_slow_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
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 = (hma_fast > hma_slow and close > hma_fast
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 = (hma_fast < hma_slow and close < hma_fast
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 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and (close < hma_fast or rsi > overbought or macd_cross_down):
self.SellMarket(self.Position)
elif self.Position < 0 and (close > hma_fast or rsi < oversold or macd_cross_up):
self.BuyMarket(-self.Position)
def CreateClone(self):
return cvd_divergence_strategy()