Volumen- und Volatilitätsverhältnis-Indikator WODI-Strategie
Vereinfachte Strategie, abgeleitet vom TradingView-Skript "Volume and Volatility Ratio Indicator - WODI". Sie überwacht das Produkt aus Volumen und Preisvolatilität, um potenzielle Umkehrungen zu erkennen. Wenn der Volatilitätsindex einen dynamischen Schwellenwert überschreitet und die letzten Kerzen eine Richtungsänderung zeigen, wird eine Position mit Fibonacci-basiertem Risikomanagement eröffnet.
Details
- Einstieg: Hohes Volumen und Volatilität kombiniert mit einem Kerzen-Umkehrmuster.
- Ausstieg: Stop-Loss und Take-Profit berechnet aus dem Kerzenbereich und Fibonacci-Multiplikatoren.
- Long/Short: Beide.
- Zeitrahmen: Beliebig.
- Indikatoren: SMA.
Dies ist eine vereinfachte Lehrversion. Die originale TradingView-Logik wurde reduziert.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Volume and volatility ratio indicator strategy (WODI).
/// Detects increased volume and volatility to enter reversal trades.
/// Uses volume MA and volatility index with short/long MA crossover.
/// </summary>
public class VolumeAndVolatilityRatioIndicatorWodiStrategy : Strategy
{
private readonly StrategyParam<int> _volLength;
private readonly StrategyParam<int> _indexLength;
private readonly StrategyParam<decimal> _stopPct;
private readonly StrategyParam<decimal> _tpPct;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = new();
private readonly List<decimal> _volIndices = new();
private decimal _entryPrice;
private decimal _stopDist;
private ICandleMessage _prevCandle;
private ICandleMessage _prevPrevCandle;
private int _cooldownRemaining;
public int VolLength { get => _volLength.Value; set => _volLength.Value = value; }
public int IndexLength { get => _indexLength.Value; set => _indexLength.Value = value; }
public decimal StopPct { get => _stopPct.Value; set => _stopPct.Value = value; }
public decimal TpPct { get => _tpPct.Value; set => _tpPct.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VolumeAndVolatilityRatioIndicatorWodiStrategy()
{
_volLength = Param(nameof(VolLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume MA Length", "Volume average period", "Parameters");
_indexLength = Param(nameof(IndexLength), 20)
.SetGreaterThanZero()
.SetDisplay("Index Length", "Volatility index average period", "Parameters");
_stopPct = Param(nameof(StopPct), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Stop %", "Stop loss percent", "Risk");
_tpPct = Param(nameof(TpPct), 1m)
.SetGreaterThanZero()
.SetDisplay("TP %", "Take profit percent", "Risk");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 24)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_volumes.Clear();
_volIndices.Clear();
_entryPrice = 0;
_stopDist = 0;
_prevCandle = null;
_prevPrevCandle = null;
_cooldownRemaining = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
_volumes.Clear();
_volIndices.Clear();
_entryPrice = 0;
_stopDist = 0;
_prevCandle = null;
_prevPrevCandle = null;
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal _dummy)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var vol = candle.TotalVolume;
var volatility = candle.ClosePrice > 0 ? (candle.HighPrice - candle.LowPrice) / candle.ClosePrice * 100m : 0;
var volIndex = vol * volatility;
_volumes.Add(vol);
_volIndices.Add(volIndex);
while (_volumes.Count > VolLength + 1)
_volumes.RemoveAt(0);
while (_volIndices.Count > IndexLength + 1)
_volIndices.RemoveAt(0);
// TP/SL management
if (Position > 0 && _entryPrice > 0 && _stopDist > 0)
{
if (candle.ClosePrice <= _entryPrice - _stopDist || candle.ClosePrice >= _entryPrice + _stopDist * (TpPct / StopPct))
{
SellMarket();
_entryPrice = 0;
_stopDist = 0;
_cooldownRemaining = SignalCooldownBars;
}
}
else if (Position < 0 && _entryPrice > 0 && _stopDist > 0)
{
if (candle.ClosePrice >= _entryPrice + _stopDist || candle.ClosePrice <= _entryPrice - _stopDist * (TpPct / StopPct))
{
BuyMarket();
_entryPrice = 0;
_stopDist = 0;
_cooldownRemaining = SignalCooldownBars;
}
}
if (_volumes.Count < VolLength || _volIndices.Count < IndexLength || _prevCandle == null || _prevPrevCandle == null)
{
_prevPrevCandle = _prevCandle;
_prevCandle = candle;
return;
}
// Calculate averages
var volAvg = _volumes.Take(VolLength).Sum() / VolLength;
var indexAvg = _volIndices.Take(IndexLength).Sum() / IndexLength;
// Entry conditions
var highVol = vol > volAvg;
var highVolIndex = volIndex > indexAvg * 2.5m;
var isLongPattern = highVol && highVolIndex
&& _prevCandle.ClosePrice < _prevPrevCandle.ClosePrice
&& candle.ClosePrice > _prevCandle.ClosePrice;
var isShortPattern = highVol && highVolIndex
&& _prevCandle.ClosePrice > _prevPrevCandle.ClosePrice
&& candle.ClosePrice < _prevCandle.ClosePrice;
if (_cooldownRemaining == 0 && isLongPattern && Position == 0)
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopDist = candle.ClosePrice * StopPct / 100m;
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && isShortPattern && Position == 0)
{
SellMarket();
_entryPrice = candle.ClosePrice;
_stopDist = candle.ClosePrice * StopPct / 100m;
_cooldownRemaining = SignalCooldownBars;
}
_prevPrevCandle = _prevCandle;
_prevCandle = candle;
}
}
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 volume_and_volatility_ratio_indicator_wodi_strategy(Strategy):
def __init__(self):
super(volume_and_volatility_ratio_indicator_wodi_strategy, self).__init__()
self._vol_length = self.Param("VolLength", 20) \
.SetDisplay("Volume MA Length", "Volume average period", "Parameters")
self._index_length = self.Param("IndexLength", 20) \
.SetDisplay("Index Length", "Volatility index average period", "Parameters")
self._stop_pct = self.Param("StopPct", 0.5) \
.SetDisplay("Stop %", "Stop loss percent", "Risk")
self._tp_pct = self.Param("TpPct", 1) \
.SetDisplay("TP %", "Take profit percent", "Risk")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 24) \
.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._volumes = []
self._vol_indices = []
self._entry_price = 0.0
self._stop_dist = 0.0
self._prev_candle = None
self._prev_prev_candle = None
self._cooldown_remaining = 0
@property
def vol_length(self):
return self._vol_length.Value
@property
def index_length(self):
return self._index_length.Value
@property
def stop_pct(self):
return self._stop_pct.Value
@property
def tp_pct(self):
return self._tp_pct.Value
@property
def signal_cooldown_bars(self):
return self._signal_cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volume_and_volatility_ratio_indicator_wodi_strategy, self).OnReseted()
self._volumes = []
self._vol_indices = []
self._entry_price = 0.0
self._stop_dist = 0.0
self._prev_candle = None
self._prev_prev_candle = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(volume_and_volatility_ratio_indicator_wodi_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = 2
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, _dummy):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
close = float(candle.ClosePrice)
vol = float(candle.TotalVolume)
volatility = (float(candle.HighPrice) - float(candle.LowPrice)) / close * 100.0 if close > 0 else 0.0
vol_index = vol * volatility
self._volumes.append(vol)
self._vol_indices.append(vol_index)
while len(self._volumes) > self.vol_length + 1:
self._volumes.pop(0)
while len(self._vol_indices) > self.index_length + 1:
self._vol_indices.pop(0)
# TP/SL management
stop_pct = float(self.stop_pct)
tp_pct = float(self.tp_pct)
if self.Position > 0 and self._entry_price > 0 and self._stop_dist > 0:
if close <= self._entry_price - self._stop_dist or close >= self._entry_price + self._stop_dist * (tp_pct / stop_pct):
self.SellMarket()
self._entry_price = 0
self._stop_dist = 0
self._cooldown_remaining = self.signal_cooldown_bars
elif self.Position < 0 and self._entry_price > 0 and self._stop_dist > 0:
if close >= self._entry_price + self._stop_dist or close <= self._entry_price - self._stop_dist * (tp_pct / stop_pct):
self.BuyMarket()
self._entry_price = 0
self._stop_dist = 0
self._cooldown_remaining = self.signal_cooldown_bars
if len(self._volumes) < self.vol_length or len(self._vol_indices) < self.index_length or self._prev_candle is None or self._prev_prev_candle is None:
self._prev_prev_candle = self._prev_candle
self._prev_candle = candle
return
# Calculate averages
vol_avg = sum(self._volumes[:self.vol_length]) / self.vol_length
index_avg = sum(self._vol_indices[:self.index_length]) / self.index_length
# Entry conditions
high_vol = vol > vol_avg
high_vol_index = vol_index > index_avg * 2.5
is_long_pattern = high_vol and high_vol_index \
and float(self._prev_candle.ClosePrice) < float(self._prev_prev_candle.ClosePrice) \
and close > float(self._prev_candle.ClosePrice)
is_short_pattern = high_vol and high_vol_index \
and float(self._prev_candle.ClosePrice) > float(self._prev_prev_candle.ClosePrice) \
and close < float(self._prev_candle.ClosePrice)
if self._cooldown_remaining == 0 and is_long_pattern and self.Position == 0:
self.BuyMarket()
self._entry_price = close
self._stop_dist = close * stop_pct / 100.0
self._cooldown_remaining = self.signal_cooldown_bars
elif self._cooldown_remaining == 0 and is_short_pattern and self.Position == 0:
self.SellMarket()
self._entry_price = close
self._stop_dist = close * stop_pct / 100.0
self._cooldown_remaining = self.signal_cooldown_bars
self._prev_prev_candle = self._prev_candle
self._prev_candle = candle
def CreateClone(self):
return volume_and_volatility_ratio_indicator_wodi_strategy()