Este enfoque estadístico busca extremos a corto plazo en el precio en relación con su promedio reciente. La estrategia utiliza una media móvil para definir el valor justo y mide la desviación de esa media mediante un cálculo de desviación estándar.
Las pruebas indican un rendimiento anual promedio de aproximadamente 85%. Funciona mejor en el mercado de criptomonedas.
Las operaciones se abren cuando el precio empuja a una distancia establecida del promedio. Una caída por debajo de la banda inferior activa una entrada larga, anticipando un rebote hacia la media, mientras que un rally por encima de la banda superior provoca un corto. Una vez que el precio toca la media móvil de nuevo, se cierra cualquier posición abierta.
El método atrae a traders con estilo contrario que desean zonas de entrada y salida claramente definidas. Debido a que se basa en bandas basadas en volatilidad, se adapta a mercados más tranquilos o más activos manteniendo las pérdidas bajo control mediante un stop-loss fijo.
Detalles
Criterios de entrada:
Largo: Price < MA - k*StdDev (below lower band)
Corto: Price > MA + k*StdDev (above upper band)
Largo/Corto: Ambos lados.
Criterios de salida:
Largo: Salir cuando el precio cruza por encima de la media móvil
Corto: Salir cuando el precio cruza por debajo de la media móvil
Stops: Sí.
Valores predeterminados:
MovingAveragePeriod = 20
DeviationMultiplier = 2.0m
StopLossPercent = 2m
CandleType = TimeSpan.FromMinutes(5)
Filtros:
Categoría: Reversión a la media
Dirección: Ambos
Indicadores: Mean Reversion
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>
/// Statistical Mean Reversion strategy.
/// Enters long when price falls below the mean by a specified number of standard deviations.
/// Enters short when price rises above the mean by a specified number of standard deviations.
/// Exits positions when price returns to the mean.
/// </summary>
public class MeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _movingAveragePeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _ma;
private StandardDeviation _stdDev;
private bool _wasBelowLower;
private bool _wasAboveUpper;
private int _cooldown;
/// <summary>
/// Moving average period parameter.
/// </summary>
public int MovingAveragePeriod
{
get => _movingAveragePeriod.Value;
set => _movingAveragePeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier parameter.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop-loss percentage parameter.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type parameter.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MeanReversionStrategy()
{
_movingAveragePeriod = Param(nameof(MovingAveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Period for moving average calculation", "Indicators")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for entry signals", "Indicators")
.SetOptimize(1.5m, 3.0m, 0.5m);
_cooldownBars = Param(nameof(CooldownBars), 50)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop-loss %", "Stop-loss as percentage of entry price", "Risk Management")
.SetOptimize(1m, 3m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_ma = null;
_stdDev = null;
_wasBelowLower = false;
_wasAboveUpper = false;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_ma = new() { Length = MovingAveragePeriod };
_stdDev = new() { Length = MovingAveragePeriod };
// Create candles subscription
var subscription = SubscribeCandles(CandleType);
// Bind indicators to subscription
subscription
.Bind(_ma, _stdDev, ProcessCandle)
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal stdDevValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Skip if strategy is not ready to trade
// Calculate upper and lower bands based on mean and standard deviation
decimal upperBand = maValue + (stdDevValue * DeviationMultiplier);
decimal lowerBand = maValue - (stdDevValue * DeviationMultiplier);
var isBelowLower = candle.ClosePrice < lowerBand;
var isAboveUpper = candle.ClosePrice > upperBand;
var crossedBelowLower = !_wasBelowLower && isBelowLower;
var crossedAboveUpper = !_wasAboveUpper && isAboveUpper;
_wasBelowLower = isBelowLower;
_wasAboveUpper = isAboveUpper;
if (_cooldown > 0)
_cooldown--;
// Trading logic
if (_cooldown == 0 && isBelowLower)
{
// Long signal: Price below lower band (mean - k*stdDev)
if (Position <= 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
else if (_cooldown == 0 && isAboveUpper)
{
// Short signal: Price above upper band (mean + k*stdDev)
if (Position >= 0)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if ((Position > 0 && candle.ClosePrice > maValue) ||
(Position < 0 && candle.ClosePrice < maValue))
{
// Exit signals: Price returned to the mean
if (Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0)
{
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, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class mean_reversion_strategy(Strategy):
"""
Statistical Mean Reversion: enters when price deviates from mean by k*stddev, exits at mean.
"""
def __init__(self):
super(mean_reversion_strategy, self).__init__()
self._ma_period = self.Param("MovingAveragePeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._dev_mult = self.Param("DeviationMultiplier", 2.0).SetDisplay("Dev Mult", "Stddev multiplier", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 50).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._was_below_lower = False
self._was_above_upper = False
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(mean_reversion_strategy, self).OnReseted()
self._was_below_lower = False
self._was_above_upper = False
self._cooldown = 0
def OnStarted2(self, time):
super(mean_reversion_strategy, self).OnStarted2(time)
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
std_dev = StandardDeviation()
std_dev.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, std_dev, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, std_val):
if candle.State != CandleStates.Finished:
return
ma = float(ma_val)
std = float(std_val)
close = float(candle.ClosePrice)
dm = self._dev_mult.Value
upper = ma + std * dm
lower = ma - std * dm
is_below = close < lower
is_above = close > upper
self._was_below_lower = is_below
self._was_above_upper = is_above
if self._cooldown > 0:
self._cooldown -= 1
if self._cooldown == 0 and is_below:
if self.Position <= 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
elif self._cooldown == 0 and is_above:
if self.Position >= 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position > 0 and close > ma:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position < 0 and close < ma:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
def CreateClone(self):
return mean_reversion_strategy()