OBV Divergenz-Strategie
On-Balance Volume verfolgt das kumulierte Handelsvolumen mit dem Gedanken, dass das Volumen dem Kurs vorausgeht. Wenn der Kurs ein neues Hoch bildet, OBV aber nicht bestätigt – oder umgekehrt –, könnte sich eine Umkehr anbahnen. Diese Strategie nutzt diese Divergenz, um gegen nicht nachhaltige Bewegungen zu handeln.
Tests zeigen eine durchschnittliche Jahresrendite von etwa 112%. Die Strategie eignet sich am besten für den Devisenmarkt.
Für jede Kerze wird OBV aktualisiert und mit der vorherigen Messung verglichen. Ein bullisches Signal entsteht, wenn der Kurs ein niedrigeres Tief bildet, während OBV ein höheres Tief ausgibt. Ein bearisches Signal tritt auf, wenn der Kurs auf ein höheres Hoch steigt, OBV jedoch zurückbleibt. Ein gleitender Durchschnitt liefert einen Ausstiegspunkt, während ein prozentualer Stop die Verluste begrenzt.
Der Ansatz versucht, Mean Reversion nach Volumenerschöpfung zu erfassen, und hält Trades oft nur, bis der Kurs wieder über die Durchschnittslinie kreuzt.
Details
- Einstiegskriterien: Kurs/OBV Divergenz.
- Long/Short: Beide.
- Ausstiegskriterien: Kurs kreuzt den gleitenden Durchschnitt oder Stop-Loss.
- Stops: Ja, prozentbasiert.
- Standardwerte:
DivergencePeriod= 5MAPeriod= 20CandleType= 5 minuteStopLossPercent= 2
- Filter:
- Kategorie: Divergenz
- Richtung: Beide
- Indikatoren: OBV, MA
- Stops: Ja
- 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>
/// OBV (On-Balance Volume) Divergence strategy.
/// Tracks OBV direction vs price direction over a lookback window.
/// Bullish divergence: price trending down but OBV trending up.
/// Bearish divergence: price trending up but OBV trending down.
/// Uses SMA for exit signals.
/// </summary>
public class ObvDivergenceStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _lookback;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _cumulativeObv;
private decimal _prevClosePrice;
private readonly List<decimal> _priceHistory = new();
private readonly List<decimal> _obvHistory = new();
private int _cooldown;
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Lookback period for divergence.
/// </summary>
public int Lookback
{
get => _lookback.Value;
set => _lookback.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Cooldown bars.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ObvDivergenceStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for SMA exit signal", "Indicators");
_lookback = Param(nameof(Lookback), 10)
.SetGreaterThanZero()
.SetDisplay("Lookback", "Lookback period for divergence detection", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_cooldownBars = Param(nameof(CooldownBars), 500)
.SetRange(1, 1000)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cumulativeObv = default;
_prevClosePrice = default;
_priceHistory.Clear();
_obvHistory.Clear();
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cumulativeObv = 0;
_prevClosePrice = 0;
_priceHistory.Clear();
_obvHistory.Clear();
_cooldown = 0;
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate OBV manually
if (_prevClosePrice > 0)
{
if (candle.ClosePrice > _prevClosePrice)
_cumulativeObv += candle.TotalVolume;
else if (candle.ClosePrice < _prevClosePrice)
_cumulativeObv -= candle.TotalVolume;
}
_prevClosePrice = candle.ClosePrice;
// Store history
_priceHistory.Add(candle.ClosePrice);
_obvHistory.Add(_cumulativeObv);
// Keep only what we need
if (_priceHistory.Count > Lookback + 1)
{
_priceHistory.RemoveAt(0);
_obvHistory.RemoveAt(0);
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_priceHistory.Count <= Lookback)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Compare current values to lookback-period-ago values
var priceChange = _priceHistory[_priceHistory.Count - 1] - _priceHistory[0];
var obvChange = _obvHistory[_obvHistory.Count - 1] - _obvHistory[0];
// Bullish divergence: price down but OBV up
var bullishDiv = priceChange < 0 && obvChange > 0;
// Bearish divergence: price up but OBV down
var bearishDiv = priceChange > 0 && obvChange < 0;
if (Position == 0 && bullishDiv)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && bearishDiv)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position > 0 && candle.ClosePrice < smaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > smaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class obv_divergence_strategy(Strategy):
"""
OBV (On-Balance Volume) Divergence strategy.
Tracks OBV direction vs price direction over a lookback window.
Bullish divergence: price trending down but OBV trending up.
Bearish divergence: price trending up but OBV trending down.
Uses SMA for exit signals.
"""
def __init__(self):
super(obv_divergence_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for SMA exit signal", "Indicators")
self._lookback = self.Param("Lookback", 10).SetDisplay("Lookback", "Lookback period for divergence detection", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._cumulative_obv = 0.0
self._prev_close = 0.0
self._price_history = []
self._obv_history = []
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(obv_divergence_strategy, self).OnReseted()
self._cumulative_obv = 0.0
self._prev_close = 0.0
self._price_history = []
self._obv_history = []
self._cooldown = 0
def OnStarted2(self, time):
super(obv_divergence_strategy, self).OnStarted2(time)
self._cumulative_obv = 0.0
self._prev_close = 0.0
self._price_history = []
self._obv_history = []
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
vol = float(candle.TotalVolume)
# Calculate OBV manually
if self._prev_close > 0:
if close > self._prev_close:
self._cumulative_obv += vol
elif close < self._prev_close:
self._cumulative_obv -= vol
self._prev_close = close
# Store history
self._price_history.append(close)
self._obv_history.append(self._cumulative_obv)
lb = self._lookback.Value
# Keep only what we need
if len(self._price_history) > lb + 1:
self._price_history.pop(0)
self._obv_history.pop(0)
if len(self._price_history) <= lb:
return
if self._cooldown > 0:
self._cooldown -= 1
return
# Compare current values to lookback-period-ago values
price_change = self._price_history[-1] - self._price_history[0]
obv_change = self._obv_history[-1] - self._obv_history[0]
# Bullish divergence: price down but OBV up
bullish_div = price_change < 0 and obv_change > 0
# Bearish divergence: price up but OBV down
bearish_div = price_change > 0 and obv_change < 0
sv = float(sma_val)
cd = self._cooldown_bars.Value
if self.Position == 0 and bullish_div:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and bearish_div:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return obv_divergence_strategy()