MACD Slope Estrategia de Reversión a la Media
La estrategia MACD Slope de Reversión a la Media se centra en lecturas extremas del MACD para aprovechar la reversión. Las desviaciones amplias del nivel normal raramente perduran.
Las operaciones se activan cuando el indicador se aleja mucho de su media y luego comienza a revertirse. Tanto las configuraciones largas como las cortas incluyen un stop de protección.
Adecuada para traders de swing que esperan oscilaciones, la estrategia cierra la posición una vez que el MACD regresa al equilibrio. Parámetro inicial FastMacdPeriod = 12.
Detalles
- Criterios de entrada: El indicador cruza de vuelta hacia la media.
- Largo/Corto: Ambas direcciones.
- Criterios de salida: El indicador revierte al promedio.
- Stops: Sí.
- Valores predeterminados:
FastMacdPeriod= 12SlowMacdPeriod= 26SignalMacdPeriod= 9LookbackPeriod= 20DeviationMultiplier= 2.0mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Reversión a la media
- Dirección: Ambos
- Indicadores: MACD
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Corto plazo
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD slope mean reversion strategy.
/// Trades reversions from extreme MACD histogram slopes and exits when the slope returns to its recent average.
/// </summary>
public class MacdSlopeMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _fastMacdPeriod;
private readonly StrategyParam<int> _slowMacdPeriod;
private readonly StrategyParam<int> _signalMacdPeriod;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _fastEma;
private decimal _slowEma;
private decimal _signalEma;
private decimal _previousHistogram;
private decimal[] _slopeHistory;
private int _currentIndex;
private int _filledCount;
private int _cooldown;
private bool _isInitialized;
/// <summary>
/// MACD fast period.
/// </summary>
public int FastMacdPeriod
{
get => _fastMacdPeriod.Value;
set => _fastMacdPeriod.Value = value;
}
/// <summary>
/// MACD slow period.
/// </summary>
public int SlowMacdPeriod
{
get => _slowMacdPeriod.Value;
set => _slowMacdPeriod.Value = value;
}
/// <summary>
/// MACD signal period.
/// </summary>
public int SignalMacdPeriod
{
get => _signalMacdPeriod.Value;
set => _signalMacdPeriod.Value = value;
}
/// <summary>
/// Period for slope statistics.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Multiplier for standard deviation to determine entry threshold.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Cooldown bars between orders.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="MacdSlopeMeanReversionStrategy"/>.
/// </summary>
public MacdSlopeMeanReversionStrategy()
{
_fastMacdPeriod = Param(nameof(FastMacdPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicator Parameters")
.SetOptimize(8, 20, 2);
_slowMacdPeriod = Param(nameof(SlowMacdPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicator Parameters")
.SetOptimize(20, 40, 2);
_signalMacdPeriod = Param(nameof(SignalMacdPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal line period for MACD", "Indicator Parameters")
.SetOptimize(5, 15, 2);
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Period for slope statistics", "Strategy Parameters")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation to determine entry threshold", "Strategy Parameters")
.SetOptimize(1m, 3m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 1200)
.SetRange(1, 5000)
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management");
_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();
_fastEma = default;
_slowEma = default;
_signalEma = default;
_previousHistogram = default;
_slopeHistory = new decimal[LookbackPeriod];
_currentIndex = default;
_filledCount = default;
_cooldown = default;
_isInitialized = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_slopeHistory = new decimal[LookbackPeriod];
_currentIndex = 0;
_filledCount = 0;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
StartProtection(new(), new Unit(StopLossPercent, UnitTypes.Percent));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var closePrice = candle.ClosePrice;
if (!_isInitialized)
{
_fastEma = closePrice;
_slowEma = closePrice;
_signalEma = 0;
_previousHistogram = 0;
_isInitialized = true;
return;
}
var fastAlpha = 2m / (FastMacdPeriod + 1m);
var slowAlpha = 2m / (SlowMacdPeriod + 1m);
var signalAlpha = 2m / (SignalMacdPeriod + 1m);
_fastEma += fastAlpha * (closePrice - _fastEma);
_slowEma += slowAlpha * (closePrice - _slowEma);
var macdLine = _fastEma - _slowEma;
_signalEma += signalAlpha * (macdLine - _signalEma);
var histogram = macdLine - _signalEma;
var histogramSlope = histogram - _previousHistogram;
_previousHistogram = histogram;
_slopeHistory[_currentIndex] = histogramSlope;
_currentIndex = (_currentIndex + 1) % LookbackPeriod;
if (_filledCount < LookbackPeriod)
_filledCount++;
if (_filledCount < LookbackPeriod)
return;
var averageSlope = 0m;
var sumSq = 0m;
for (var i = 0; i < LookbackPeriod; i++)
averageSlope += _slopeHistory[i];
averageSlope /= LookbackPeriod;
for (var i = 0; i < LookbackPeriod; i++)
{
var diff = _slopeHistory[i] - averageSlope;
sumSq += diff * diff;
}
var slopeStdDev = (decimal)Math.Sqrt((double)(sumSq / LookbackPeriod));
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var lowerThreshold = averageSlope - DeviationMultiplier * slopeStdDev;
var upperThreshold = averageSlope + DeviationMultiplier * slopeStdDev;
if (Position == 0)
{
if (histogramSlope < lowerThreshold && histogram < 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (histogramSlope > upperThreshold && histogram > 0)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0 && histogramSlope >= averageSlope)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && histogramSlope <= averageSlope)
{
BuyMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
}
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, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Strategies import Strategy
class macd_slope_mean_reversion_strategy(Strategy):
"""
MACD slope mean reversion strategy.
Trades reversions from extreme MACD histogram slopes and exits when the slope returns to its recent average.
"""
def __init__(self):
super(macd_slope_mean_reversion_strategy, self).__init__()
self._fast_macd_period = self.Param("FastMacdPeriod", 12) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast", "Fast EMA period for MACD", "Indicator Parameters")
self._slow_macd_period = self.Param("SlowMacdPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow", "Slow EMA period for MACD", "Indicator Parameters")
self._signal_macd_period = self.Param("SignalMacdPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal", "Signal line period for MACD", "Indicator Parameters")
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Period for slope statistics", "Strategy Parameters")
self._deviation_multiplier = self.Param("DeviationMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation to determine entry threshold", "Strategy Parameters")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1200) \
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_ema = 0.0
self._slow_ema = 0.0
self._signal_ema = 0.0
self._previous_histogram = 0.0
self._slope_history = None
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._is_initialized = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(macd_slope_mean_reversion_strategy, self).OnReseted()
self._fast_ema = 0.0
self._slow_ema = 0.0
self._signal_ema = 0.0
self._previous_histogram = 0.0
lb = int(self._lookback_period.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._is_initialized = False
def OnStarted2(self, time):
super(macd_slope_mean_reversion_strategy, self).OnStarted2(time)
lb = int(self._lookback_period.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
self.StartProtection(Unit(), Unit(self._stop_loss_percent.Value, UnitTypes.Percent))
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close_price = float(candle.ClosePrice)
if not self._is_initialized:
self._fast_ema = close_price
self._slow_ema = close_price
self._signal_ema = 0.0
self._previous_histogram = 0.0
self._is_initialized = True
return
fast_period = float(self._fast_macd_period.Value)
slow_period = float(self._slow_macd_period.Value)
signal_period = float(self._signal_macd_period.Value)
fast_alpha = 2.0 / (fast_period + 1.0)
slow_alpha = 2.0 / (slow_period + 1.0)
signal_alpha = 2.0 / (signal_period + 1.0)
self._fast_ema += fast_alpha * (close_price - self._fast_ema)
self._slow_ema += slow_alpha * (close_price - self._slow_ema)
macd_line = self._fast_ema - self._slow_ema
self._signal_ema += signal_alpha * (macd_line - self._signal_ema)
histogram = macd_line - self._signal_ema
histogram_slope = histogram - self._previous_histogram
self._previous_histogram = histogram
lb = int(self._lookback_period.Value)
self._slope_history[self._current_index] = histogram_slope
self._current_index = (self._current_index + 1) % lb
if self._filled_count < lb:
self._filled_count += 1
if self._filled_count < lb:
return
avg_slope = 0.0
for i in range(lb):
avg_slope += self._slope_history[i]
avg_slope /= float(lb)
sum_sq = 0.0
for i in range(lb):
diff = self._slope_history[i] - avg_slope
sum_sq += diff * diff
std_slope = math.sqrt(sum_sq / float(lb))
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
dm = float(self._deviation_multiplier.Value)
lower_threshold = avg_slope - dm * std_slope
upper_threshold = avg_slope + dm * std_slope
if self.Position == 0:
if histogram_slope < lower_threshold and histogram < 0:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif histogram_slope > upper_threshold and histogram > 0:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position > 0 and histogram_slope >= avg_slope:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position < 0 and histogram_slope <= avg_slope:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
def CreateClone(self):
return macd_slope_mean_reversion_strategy()