Estrategia de Reversión a la Media OBV
El On Balance Volume (OBV) rastrea el flujo acumulativo de volumen para determinar si los compradores o vendedores son dominantes. Esta estrategia espera a que el OBV diverja marcadamente de su promedio y luego opera anticipando un retorno a niveles típicos.
Las pruebas indican un rendimiento anual promedio de aproximadamente 79%. Funciona mejor en el mercado de acciones.
Una señal de compra ocurre cuando el OBV cae por debajo de su media menos Multiplier veces la desviación estándar y el precio está por debajo de la media móvil. Una señal de venta se genera cuando el OBV sube por encima de la banda superior con el precio por encima de la media. Las posiciones se cierran cuando el OBV cruza de vuelta a través de su línea media.
El enfoque es útil para traders que consideran los flujos de volumen además de la acción del precio. Los stops se colocan a un porcentaje establecido para manejar situaciones donde el volumen continúa acelerando.
Detalles
- Criterios de entrada:
- Largo: OBV < Avg - Multiplier * StdDev && Close < MA
- Corto: OBV > Avg + Multiplier * StdDev && Close > MA
- Largo/Corto: Ambos lados.
- Criterios de salida:
- Largo: Salir cuando OBV > Avg
- Corto: Salir cuando OBV < Avg
- Stops: Sí, stop-loss porcentual.
- Valores predeterminados:
AveragePeriod= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Reversión a la media
- Dirección: Ambos
- Indicadores: OBV
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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>
/// OBV Mean Reversion Strategy (244).
/// Enter when OBV deviates from its average by a certain multiple of standard deviation.
/// Exit when OBV returns to its average.
/// </summary>
public class ObvMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private OnBalanceVolume _obv;
private SimpleMovingAverage _obvAverage;
private StandardDeviation _obvStdDev;
private decimal? _currentObv;
private decimal? _obvAvgValue;
private decimal? _obvStdDevValue;
/// <summary>
/// Period for OBV average calculation.
/// </summary>
public int AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for entry.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ObvMeanReversionStrategy"/>.
/// </summary>
public ObvMeanReversionStrategy()
{
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period for OBV average calculation", "Strategy Parameters")
.SetOptimize(10, 30, 5);
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("StdDev Multiplier", "Standard deviation multiplier for entry", "Strategy Parameters")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "Strategy Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_currentObv = default;
_obvAvgValue = default;
_obvStdDevValue = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_obv = new OnBalanceVolume();
_obvAverage = new SMA { Length = AveragePeriod };
_obvStdDev = new StandardDeviation { Length = AveragePeriod };
// Create candle subscription
var subscription = SubscribeCandles(CandleType);
// Create processing chain
subscription
.BindEx(_obv, ProcessObv)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _obv);
DrawOwnTrades(area);
}
// Enable position protection
StartProtection(
takeProfit: new Unit(5, UnitTypes.Percent),
stopLoss: new Unit(2, UnitTypes.Percent)
);
}
private void ProcessObv(ICandleMessage candle, IIndicatorValue obvValue)
{
if (candle.State != CandleStates.Finished)
return;
// Extract OBV value
_currentObv = obvValue.ToDecimal();
// Process OBV through average and standard deviation indicators
var avgIndicatorValue = _obvAverage.Process(obvValue);
var stdDevIndicatorValue = _obvStdDev.Process(obvValue);
_obvAvgValue = avgIndicatorValue.ToDecimal();
_obvStdDevValue = stdDevIndicatorValue.ToDecimal();
// Check if strategy is ready for trading
if (!IsFormedAndOnlineAndAllowTrading() || !_obvAverage.IsFormed || !_obvStdDev.IsFormed)
return;
// Ensure we have all needed values
if (!_currentObv.HasValue || !_obvAvgValue.HasValue || !_obvStdDevValue.HasValue)
return;
// Calculate bands
var upperBand = _obvAvgValue.Value + Multiplier * _obvStdDevValue.Value;
var lowerBand = _obvAvgValue.Value - Multiplier * _obvStdDevValue.Value;
LogInfo($"OBV: {_currentObv}, OBV Avg: {_obvAvgValue}, Upper: {upperBand}, Lower: {lowerBand}");
// Entry logic
if (Position == 0)
{
// Long Entry: OBV is below lower band (OBV oversold)
if (_currentObv.Value < lowerBand)
{
LogInfo($"Buy Signal - OBV ({_currentObv}) < Lower Band ({lowerBand})");
BuyMarket(Volume);
}
// Short Entry: OBV is above upper band (OBV overbought)
else if (_currentObv.Value > upperBand)
{
LogInfo($"Sell Signal - OBV ({_currentObv}) > Upper Band ({upperBand})");
SellMarket(Volume);
}
}
// Exit logic
else if (Position > 0 && _currentObv.Value > _obvAvgValue.Value)
{
// Exit Long: OBV returned to average
LogInfo($"Exit Long - OBV ({_currentObv}) > OBV Avg ({_obvAvgValue})");
SellMarket(Math.Abs(Position));
}
else if (Position < 0 && _currentObv.Value < _obvAvgValue.Value)
{
// Exit Short: OBV returned to average
LogInfo($"Exit Short - OBV ({_currentObv}) < OBV Avg ({_obvAvgValue})");
BuyMarket(Math.Abs(Position));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Indicators import OnBalanceVolume, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class obv_mean_reversion_strategy(Strategy):
"""
OBV Mean Reversion Strategy (244).
Enter when OBV deviates from its average by a certain multiple of standard deviation.
Exit when OBV returns to its average.
"""
def __init__(self):
super(obv_mean_reversion_strategy, self).__init__()
# Initialize strategy parameters
self._average_period = self.Param("AveragePeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Average Period", "Period for OBV average calculation", "Strategy Parameters") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 5)
self._multiplier = self.Param("Multiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("StdDev Multiplier", "Standard deviation multiplier for entry", "Strategy Parameters") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "Strategy Parameters")
# Internal fields
self._obv = None
self._obv_average = None
self._obv_std_dev = None
self._current_obv = None
self._obv_avg_value = None
self._obv_std_dev_value = None
@property
def AveragePeriod(self):
"""Period for OBV average calculation."""
return self._average_period.Value
@AveragePeriod.setter
def AveragePeriod(self, value):
self._average_period.Value = value
@property
def Multiplier(self):
"""Standard deviation multiplier for entry."""
return self._multiplier.Value
@Multiplier.setter
def Multiplier(self, value):
self._multiplier.Value = value
@property
def CandleType(self):
"""Type of candles to use."""
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(obv_mean_reversion_strategy, self).OnReseted()
self._current_obv = None
self._obv_avg_value = None
self._obv_std_dev_value = None
def OnStarted2(self, time):
super(obv_mean_reversion_strategy, self).OnStarted2(time)
# Create indicators
self._obv = OnBalanceVolume()
self._obv_average = SimpleMovingAverage()
self._obv_average.Length = self.AveragePeriod
self._obv_std_dev = StandardDeviation()
self._obv_std_dev.Length = self.AveragePeriod
# Create candle subscription
subscription = self.SubscribeCandles(self.CandleType)
# Create processing chain
subscription.BindEx(self._obv, self.ProcessObv).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._obv)
self.DrawOwnTrades(area)
# Enable position protection
self.StartProtection(
takeProfit=Unit(5, UnitTypes.Percent),
stopLoss=Unit(2, UnitTypes.Percent)
)
def ProcessObv(self, candle, obv_value):
if candle.State != CandleStates.Finished:
return
# Extract OBV value
self._current_obv = float(obv_value)
# Process OBV through average and standard deviation indicators
avg_indicator_value = self._obv_average.Process(obv_value)
std_dev_indicator_value = self._obv_std_dev.Process(obv_value)
self._obv_avg_value = float(avg_indicator_value)
self._obv_std_dev_value = float(std_dev_indicator_value)
# Check if strategy is ready for trading
if not self.IsFormedAndOnlineAndAllowTrading() or not self._obv_average.IsFormed or not self._obv_std_dev.IsFormed:
return
# Ensure we have all needed values
if self._current_obv is None or self._obv_avg_value is None or self._obv_std_dev_value is None:
return
# Calculate bands
upper_band = self._obv_avg_value + self.Multiplier * self._obv_std_dev_value
lower_band = self._obv_avg_value - self.Multiplier * self._obv_std_dev_value
self.LogInfo("OBV: {0}, OBV Avg: {1}, Upper: {2}, Lower: {3}".format(
self._current_obv, self._obv_avg_value, upper_band, lower_band))
# Entry logic
if self.Position == 0:
# Long Entry: OBV is below lower band (OBV oversold)
if self._current_obv < lower_band:
self.LogInfo("Buy Signal - OBV ({0}) < Lower Band ({1})".format(self._current_obv, lower_band))
self.BuyMarket(self.Volume)
# Short Entry: OBV is above upper band (OBV overbought)
elif self._current_obv > upper_band:
self.LogInfo("Sell Signal - OBV ({0}) > Upper Band ({1})".format(self._current_obv, upper_band))
self.SellMarket(self.Volume)
# Exit logic
elif self.Position > 0 and self._current_obv > self._obv_avg_value:
# Exit Long: OBV returned to average
self.LogInfo("Exit Long - OBV ({0}) > OBV Avg ({1})".format(self._current_obv, self._obv_avg_value))
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0 and self._current_obv < self._obv_avg_value:
# Exit Short: OBV returned to average
self.LogInfo("Exit Short - OBV ({0}) < OBV Avg ({1})".format(self._current_obv, self._obv_avg_value))
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
"""
!! REQUIRED!! Creates a new instance of the strategy.
"""
return obv_mean_reversion_strategy()