Modifizierter OBV mit Divergenz-Erkennung
Diese Strategie glättet den On-Balance Volume (OBV) mit einem wählbaren gleitenden Durchschnitt und erzeugt eine Signallinie. Trades entstehen, wenn der geglättete OBV die Signallinie kreuzt. Zusätzlich protokolliert die Strategie reguläre und versteckte Divergenzen zwischen Preis und OBV mithilfe von Fraktal-Erkennung.
Details
- Einstiegskriterien: OBV-M kreuzt die Signallinie von unten/oben.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: Entgegengesetzter Kreuzungspunkt.
- Stops: Nein.
- Standardwerte:
MaType= ExponentialObvMaLength= 7SignalLength= 10CandleType= TimeSpan.FromMinutes(5)
- Filter:
- Kategorie: Divergenz
- Richtung: Beide
- Indikatoren: OBV, MA
- Stops: Nein
- Komplexität: Mittel
- Zeitrahmen: Intraday
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Ja
- Risikolevel: Mittel
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Modified On-Balance Volume strategy with divergence detection.
/// Enters long when OBV-M crosses above its signal line,
/// enters short when OBV-M crosses below its signal line.
/// </summary>
public class ModifiedObvWithDivergenceDetectionStrategy : Strategy
{
private readonly StrategyParam<int> _obvMaLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<decimal> _minCrossGapPercent;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private OnBalanceVolume _obv;
private EMA _obvMa;
private EMA _signalMa;
private bool _wasBelowSignal;
private bool _isInitialized;
private int _barsFromSignal;
public int ObvMaLength { get => _obvMaLength.Value; set => _obvMaLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public decimal MinCrossGapPercent { get => _minCrossGapPercent.Value; set => _minCrossGapPercent.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ModifiedObvWithDivergenceDetectionStrategy()
{
_obvMaLength = Param(nameof(ObvMaLength), 7).SetGreaterThanZero();
_signalLength = Param(nameof(SignalLength), 10).SetGreaterThanZero();
_minCrossGapPercent = Param(nameof(MinCrossGapPercent), 0.2m).SetGreaterThanZero();
_signalCooldownBars = Param(nameof(SignalCooldownBars), 10).SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_obv = null;
_obvMa = null;
_signalMa = null;
_wasBelowSignal = false;
_isInitialized = false;
_barsFromSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_isInitialized = false;
_barsFromSignal = SignalCooldownBars;
_obv = new OnBalanceVolume();
_obvMa = new EMA { Length = ObvMaLength };
_signalMa = new EMA { Length = SignalLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_obv, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue obvValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_obv.IsFormed)
return;
var obvmResult = _obvMa.Process(obvValue);
var obvm = obvmResult.ToDecimal();
var signal = _signalMa.Process(obvmResult).ToDecimal();
if (!_obvMa.IsFormed || !_signalMa.IsFormed)
return;
if (!_isInitialized)
{
_wasBelowSignal = obvm < signal;
_isInitialized = true;
return;
}
var isBelow = obvm < signal;
var denominator = Math.Abs(signal) + 1m;
var gapPercent = Math.Abs(obvm - signal) / denominator * 100m;
_barsFromSignal++;
if (_barsFromSignal >= SignalCooldownBars && gapPercent >= MinCrossGapPercent && _wasBelowSignal && !isBelow && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket();
_barsFromSignal = 0;
}
else if (_barsFromSignal >= SignalCooldownBars && gapPercent >= MinCrossGapPercent && !_wasBelowSignal && isBelow && Position >= 0)
{
if (Position > 0)
SellMarket(Position);
SellMarket();
_barsFromSignal = 0;
}
_wasBelowSignal = isBelow;
}
}
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 OnBalanceVolume, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class modified_obv_with_divergence_detection_strategy(Strategy):
def __init__(self):
super(modified_obv_with_divergence_detection_strategy, self).__init__()
self._obv_ma_length = self.Param("ObvMaLength", 7) \
.SetGreaterThanZero()
self._signal_length = self.Param("SignalLength", 10) \
.SetGreaterThanZero()
self._min_cross_gap_percent = self.Param("MinCrossGapPercent", 0.2) \
.SetGreaterThanZero()
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 10) \
.SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._was_below_signal = False
self._is_initialized = False
self._bars_from_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(modified_obv_with_divergence_detection_strategy, self).OnReseted()
self._was_below_signal = False
self._is_initialized = False
self._bars_from_signal = 0
def OnStarted2(self, time):
super(modified_obv_with_divergence_detection_strategy, self).OnStarted2(time)
self._is_initialized = False
self._bars_from_signal = self._signal_cooldown_bars.Value
self._obv = OnBalanceVolume()
self._obv_ma = ExponentialMovingAverage()
self._obv_ma.Length = self._obv_ma_length.Value
self._signal_ma = ExponentialMovingAverage()
self._signal_ma.Length = self._signal_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._obv, self.OnProcess).Start()
def OnProcess(self, candle, obv_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if not self._obv.IsFormed:
return
obvm_result = self._obv_ma.Process(obv_value)
obvm = float(obvm_result)
signal_result = self._signal_ma.Process(obvm_result)
signal = float(signal_result)
if not self._obv_ma.IsFormed or not self._signal_ma.IsFormed:
return
if not self._is_initialized:
self._was_below_signal = obvm < signal
self._is_initialized = True
return
is_below = obvm < signal
denominator = abs(signal) + 1.0
gap_percent = abs(obvm - signal) / denominator * 100.0
self._bars_from_signal += 1
cd = self._signal_cooldown_bars.Value
min_gap = float(self._min_cross_gap_percent.Value)
if self._bars_from_signal >= cd and gap_percent >= min_gap and self._was_below_signal and not is_below and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self.BuyMarket()
self._bars_from_signal = 0
elif self._bars_from_signal >= cd and gap_percent >= min_gap and not self._was_below_signal and is_below and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket()
self._bars_from_signal = 0
self._was_below_signal = is_below
def CreateClone(self):
return modified_obv_with_divergence_detection_strategy()