BabyShark VWAP-Strategie
Diese Strategie kombiniert ein volumengewichtetes Durchschnittspreis-Band (VWAP) mit einem OBV-basierten RSI-Filter. Long-Trades entstehen, wenn der Preis unter das untere Abweichungsband fällt und der RSI überverkauft signalisiert. Short-Trades werden ausgelöst, wenn der Preis über das obere Band steigt und der RSI überkauft ist.
Stops verwenden einen kleinen prozentualen Verlust, und Positionen warten eine Abkühlungsperiode vor dem Wiedereinstieg ab.
Details
- Einstiegskriterien: Preis kreuzt Abweichungsbänder mit RSI-Bestätigung.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: Rückkehr zum VWAP oder Stop-Loss.
- Stops: Ja.
- Standardwerte:
Length= 60RsiLength= 5HigherLevel= 70LowerLevel= 30Cooldown= 10StopLossPercent= 0.6mCandleType= TimeSpan.FromMinutes(1)
- Filter:
- Kategorie: Mean Reversion
- Richtung: Beide
- Indikatoren: VWAP, RSI, OBV
- 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>
/// BabyShark VWAP strategy.
/// A rolling VWAP of the typical price over Length candles carries bands two volume-weighted standard deviations away, and an
/// RSI of On-Balance Volume confirms extremes. A close below the lower band with OBV RSI below LowerLevel goes long and a close
/// above the upper band with OBV RSI above HigherLevel goes short. A long closes when price returns to the VWAP from below and
/// a short when it returns from above; a StopLossPercent stop protects both. After a position is closed no new entry is
/// taken for Cooldown candles.
/// </summary>
public class BabySharkVwapStrategy : Strategy
{
private const decimal _bandDeviations = 2m;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _higherLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private readonly StrategyParam<int> _cooldown;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<(decimal price, decimal volume)> _window = new();
private RelativeStrengthIndex _obvRsi;
private decimal _obv;
private decimal? _prevClose;
private int _barIndex;
private int? _lastExitBar;
private bool _wasInPosition;
/// <summary>
/// Rolling VWAP window.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// RSI period applied to OBV.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// OBV RSI level that confirms shorts.
/// </summary>
public decimal HigherLevel
{
get => _higherLevel.Value;
set => _higherLevel.Value = value;
}
/// <summary>
/// OBV RSI level that confirms longs.
/// </summary>
public decimal LowerLevel
{
get => _lowerLevel.Value;
set => _lowerLevel.Value = value;
}
/// <summary>
/// Candles to wait after a position closes.
/// </summary>
public int Cooldown
{
get => _cooldown.Value;
set => _cooldown.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BabySharkVwapStrategy()
{
_length = Param(nameof(Length), 60)
.SetGreaterThanZero()
.SetDisplay("Length", "Rolling VWAP window", "VWAP");
_rsiLength = Param(nameof(RsiLength), 5)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period applied to OBV", "RSI");
_higherLevel = Param(nameof(HigherLevel), 70m)
.SetDisplay("Higher Level", "OBV RSI level that confirms shorts", "RSI");
_lowerLevel = Param(nameof(LowerLevel), 30m)
.SetDisplay("Lower Level", "OBV RSI level that confirms longs", "RSI");
_cooldown = Param(nameof(Cooldown), 10)
.SetNotNegative()
.SetDisplay("Cooldown", "Candles to wait after a position closes", "Trading");
_stopLossPercent = Param(nameof(StopLossPercent), 0.6m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk");
_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();
_obvRsi = null;
ResetState();
}
private void ResetState()
{
_window.Clear();
_obv = 0m;
_prevClose = null;
_barIndex = 0;
_lastExitBar = null;
_wasInPosition = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_obvRsi = new RelativeStrengthIndex { Length = RsiLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), StopLossPercent > 0m ? new Unit(StopLossPercent, UnitTypes.Percent) : new Unit(), useMarketOrders: true);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_barIndex++;
var close = candle.ClosePrice;
var volume = candle.TotalVolume;
if (_prevClose is decimal pc)
_obv += close > pc ? volume : close < pc ? -volume : 0m;
_prevClose = close;
var obvRsiValue = _obvRsi.Process(_obv, candle.ServerTime, true);
var typical = (candle.HighPrice + candle.LowPrice + close) / 3m;
_window.Enqueue((typical, volume));
if (_window.Count > Length)
_window.Dequeue();
// A position that has disappeared since the last candle was closed, by the stop or by an exit.
if (_wasInPosition && Position == 0)
_lastExitBar = _barIndex;
_wasInPosition = Position != 0;
if (_window.Count < Length || !_obvRsi.IsFormed || obvRsiValue.IsEmpty)
return;
var obvRsi = obvRsiValue.ToDecimal();
var sumPv = 0m;
var sumV = 0m;
foreach (var (price, v) in _window)
{
sumPv += price * v;
sumV += v;
}
if (sumV <= 0m)
return;
var vwap = sumPv / sumV;
var sumSq = 0m;
foreach (var (price, v) in _window)
sumSq += v * (price - vwap) * (price - vwap);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var variance = sumSq / sumV;
var deviation = (decimal)Math.Sqrt((double)variance);
var upper = vwap + _bandDeviations * deviation;
var lower = vwap - _bandDeviations * deviation;
if (Position > 0)
{
if (close >= vwap)
{
SellMarket(Position);
_lastExitBar = _barIndex;
}
return;
}
if (Position < 0)
{
if (close <= vwap)
{
BuyMarket(-Position);
_lastExitBar = _barIndex;
}
return;
}
if (_lastExitBar is int last && _barIndex - last < Cooldown)
return;
if (close < lower && obvRsi < LowerLevel)
{
BuyMarket(Volume);
_wasInPosition = true;
}
else if (close > upper && obvRsi > HigherLevel)
{
SellMarket(Volume);
_wasInPosition = true;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
BAND_DEVIATIONS = 2.0
class baby_shark_vwap_strategy(Strategy):
"""
BabyShark VWAP strategy.
A rolling VWAP of the typical price over Length candles carries bands two volume-weighted standard deviations away, and an
RSI of On-Balance Volume confirms extremes. A close below the lower band with OBV RSI below LowerLevel goes long and a close
above the upper band with OBV RSI above HigherLevel goes short. A long closes when price returns to the VWAP from below and
a short when it returns from above; a StopLossPercent stop protects both. After a position is closed no new entry is
taken for Cooldown candles.
"""
def __init__(self):
super(baby_shark_vwap_strategy, self).__init__()
self._length = self.Param("Length", 60) \
.SetGreaterThanZero() \
.SetDisplay("Length", "Rolling VWAP window", "VWAP")
self._rsi_length = self.Param("RsiLength", 5) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period applied to OBV", "RSI")
self._higher_level = self.Param("HigherLevel", 70.0) \
.SetDisplay("Higher Level", "OBV RSI level that confirms shorts", "RSI")
self._lower_level = self.Param("LowerLevel", 30.0) \
.SetDisplay("Lower Level", "OBV RSI level that confirms longs", "RSI")
self._cooldown = self.Param("Cooldown", 10) \
.SetNotNegative() \
.SetDisplay("Cooldown", "Candles to wait after a position closes", "Trading")
self._stop_loss_percent = self.Param("StopLossPercent", 0.6) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._obv_rsi = None
self._reset_state()
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def _reset_state(self):
self._window = []
self._obv = 0.0
self._prev_close = None
self._bar_index = 0
self._last_exit_bar = None
self._was_in_position = False
def OnReseted(self):
super(baby_shark_vwap_strategy, self).OnReseted()
self._obv_rsi = None
self._reset_state()
def OnStarted2(self, time):
super(baby_shark_vwap_strategy, self).OnStarted2(time)
self._reset_state()
self._obv_rsi = RelativeStrengthIndex()
self._obv_rsi.Length = self._rsi_length.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
stop = float(self._stop_loss_percent.Value)
self.StartProtection(Unit(), Unit(Decimal(stop), UnitTypes.Percent) if stop > 0 else Unit(), useMarketOrders=True)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._bar_index += 1
close = float(candle.ClosePrice)
volume = float(candle.TotalVolume)
if self._prev_close is not None:
if close > self._prev_close:
self._obv += volume
elif close < self._prev_close:
self._obv -= volume
self._prev_close = close
obv_rsi_value = process_float(self._obv_rsi, self._obv, candle.ServerTime, True)
typical = (float(candle.HighPrice) + float(candle.LowPrice) + close) / 3.0
self._window.append((typical, volume))
length = self._length.Value
if len(self._window) > length:
self._window.pop(0)
# A position that has disappeared since the last candle was closed, by the stop or by an exit.
if self._was_in_position and self.Position == 0:
self._last_exit_bar = self._bar_index
self._was_in_position = self.Position != 0
if len(self._window) < length or not self._obv_rsi.IsFormed or obv_rsi_value.IsEmpty:
return
obv_rsi = float(to_decimal(obv_rsi_value))
sum_pv = sum(p * v for p, v in self._window)
sum_v = sum(v for _, v in self._window)
if sum_v <= 0:
return
vwap = sum_pv / sum_v
sum_sq = sum(v * (p - vwap) * (p - vwap) for p, v in self._window)
if not self.IsFormedAndOnlineAndAllowTrading():
return
deviation = math.sqrt(max(0.0, sum_sq / sum_v))
upper = vwap + BAND_DEVIATIONS * deviation
lower = vwap - BAND_DEVIATIONS * deviation
if self.Position > 0:
if close >= vwap:
self.SellMarket(self.Position)
self._last_exit_bar = self._bar_index
return
if self.Position < 0:
if close <= vwap:
self.BuyMarket(-self.Position)
self._last_exit_bar = self._bar_index
return
if self._last_exit_bar is not None and self._bar_index - self._last_exit_bar < self._cooldown.Value:
return
if close < lower and obv_rsi < float(self._lower_level.Value):
self.BuyMarket(self.Volume)
self._was_in_position = True
elif close > upper and obv_rsi > float(self._higher_level.Value):
self.SellMarket(self.Volume)
self._was_in_position = True
def CreateClone(self):
return baby_shark_vwap_strategy()