Auf GitHub ansehen

Strategie OBV Breakout

On-Balance Volume (OBV) verfolgt den Kauf- und Verkaufsdruck durch Akkumulation von Volumen. Diese Strategie sucht nach einem OBV-Ausbruch über ein Hoch oder unter ein Tief innerhalb des Beobachtungsfensters, während der Preis die Bewegung bestätigt.

Tests zeigen eine durchschnittliche jährliche Rendite von etwa 178%. Am besten funktioniert es im Aktienmarkt.

Ein Ausbruch im OBV deutet auf starkes Interesse hin. Das System geht long, wenn OBV sein vorheriges Maximum überschreitet, oder short, wenn es das Minimum unterschreitet. Das Kreuzen des OBV mit seinem gleitenden Durchschnitt signalisiert einen Ausstieg.

Dies verbindet Volumen-Momentum mit Preisaktionen.

Details

  • Einstiegskriterien: OBV überschreitet den höchsten oder niedrigsten Wert im Lookback-Zeitraum.
  • Long/Short: Beide Richtungen.
  • Ausstiegskriterien: OBV kreuzt seinen MA oder Stop.
  • Stops: Ja.
  • Standardwerte:
    • LookbackPeriod = 20
    • OBVMAPeriod = 20
    • CandleType = TimeSpan.FromMinutes(5)
  • Filter:
    • Kategorie: Ausbruch
    • Richtung: Beide
    • Indikatoren: OBV, MA
    • Stops: Ja
    • Komplexität: Mittel
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • 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>
/// On-Balance Volume (OBV) Breakout strategy.
/// Enters when OBV crosses its moving average indicating volume confirmation of trend.
/// </summary>
public class ObvBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private decimal _prevObv;
	private int _cooldown;

	/// <summary>
	/// MA Period for OBV average.
	/// </summary>
	public int MAPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Candle type for strategy calculation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Cooldown bars between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Initialize the OBV Breakout strategy.
	/// </summary>
	public ObvBreakoutStrategy()
	{
		_maPeriod = Param(nameof(MAPeriod), 20)
			.SetDisplay("MA Period", "Period for OBV moving average", "Indicators")
			.SetOptimize(10, 50, 10);

		_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();
		_prevObv = default;
		_cooldown = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevObv = 0;
		_cooldown = 0;

		var obv = new OnBalanceVolume();
		var sma = new SimpleMovingAverage { Length = MAPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(obv, sma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal obvValue, decimal smaValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_prevObv == 0)
		{
			_prevObv = obvValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevObv = obvValue;
			return;
		}

		// Use OBV direction + price vs SMA for signals
		var obvRising = obvValue > _prevObv;

		if (Position == 0)
		{
			if (obvRising && candle.ClosePrice > smaValue)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
			else if (!obvRising && candle.ClosePrice < smaValue)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
		}
		else if (Position > 0 && !obvRising)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && obvRising)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}

		_prevObv = obvValue;
	}
}