Стратегия CVD Divergence Volume HMA RSI MACD
Эта стратегия объединяет Hull Moving Average, RSI, MACD, фильтр по объему и дивергенцию кумулятивного дельта-объема (CVD) для поиска трендовых возможностей.
Длинная позиция открывается, когда HMA20 выше HMA50, RSI подтверждает бычий импульс, гистограмма MACD растет, объем превышает среднее, а CVD показывает бычью дивергенцию или растет. Короткая позиция возникает при зеркальных медвежьих условиях.
Подробности
- Условия входа:
- Long: HMA20 > HMA50 и цена выше HMA20; RSI между 40 и
RsiOverbought; линия MACD выше сигнальной и гистограмма растет; объем > SMA *VolumeMultiplier; CVD формирует бычью дивергенцию или растет. - Short: HMA20 < HMA50 и цена ниже HMA20; RSI между
RsiOversoldи 60; линия MACD ниже сигнальной и гистограмма падает; объем > SMA *VolumeMultiplier; CVD формирует медвежью дивергенцию или снижается.
- Long: HMA20 > HMA50 и цена выше HMA20; RSI между 40 и
- Long/Short: обе стороны.
- Условия выхода:
- Long: цена < HMA20 или RSI >
RsiOverboughtили линия MACD опускается ниже сигнальной. - Short: цена > HMA20 или RSI <
RsiOversoldили линия MACD поднимается выше сигнальной.
- Long: цена < HMA20 или RSI >
- Стопы: нет.
- Значения по умолчанию:
Hma20Length= 20Hma50Length= 50RsiLength= 14RsiOverbought= 70RsiOversold= 30MacdFast= 12MacdSlow= 26MacdSignal= 9VolumeMaLength= 20VolumeMultiplier= 1.5CvdLength= 14DivergenceLookback= 5CandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Смешанная
- Направление: Обе
- Индикаторы: HMA, RSI, MACD, Объем, CVD
- Стопы: Нет
- Сложность: Продвинутая
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Да
- Уровень риска: Средний
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()