CVDダイバージェンス戦略
この戦略は累積出来高デルタ(CVD)のダイバージェンスをHull Moving Average、RSI、MACD、出来高フィルターと組み合わせる。トレンド、モメンタム、出来高が一致し、CVDがダイバージェンスまたはトレード方向への継続を示したときにポジションを開く。逆シグナルまたはインジケーターのクロスでポジションを閉じる。
詳細
- エントリー条件: HMAによるトレンドの整合、RSIとMACDの確認、高出来高とCVDダイバージェンス/継続。
- ロング/ショート: 両方向。
- エグジット条件: 逆シグナルまたはインジケーターのクロス。
- ストップ: 明示的なストップなし。
- デフォルト値:
HmaFastLength= 20HmaSlowLength= 50RsiLength= 14RsiOverbought= 70RsiOversold= 30MacdFast= 12MacdSlow= 26MacdSignal= 9VolumeMaLength= 20VolumeMultiplier= 1.5mCvdLength= 14DivergenceLookback= 5CandleType= TimeSpan.FromMinutes(1)
- フィルター:
- カテゴリ: ダイバージェンス
- 方向: 両方
- インジケーター: HMA、RSI、MACD、出来高
- ストップ: いいえ
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: はい
- リスクレベル: 中
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()