Estrategia de Reversión a la Media por Ancho de Nube Ichimoku
La estrategia de Reversión a la Media por Ancho de Nube Ichimoku se centra en lecturas extremas del indicador Ichimoku para explotar la reversión. Las grandes desviaciones del nivel normal rara vez perduran.
Las pruebas indican un rendimiento anual promedio de aproximadamente 124%. Funciona mejor en el mercado de divisas.
Las operaciones se activan cuando el indicador se aleja mucho de su media y luego comienza a revertirse. Tanto las configuraciones largas como cortas incluyen un stop protector.
Adecuada para operadores de swing que esperan oscilaciones, la estrategia cierra las posiciones una vez que el indicador Ichimoku regresa al equilibrio. Parámetro inicial TenkanPeriod = 9.
Detalles
- Criterios de entrada: El indicador cruza de regreso hacia la media.
- Largo/Corto: Ambas direcciones.
- Criterios de salida: El indicador revierte al promedio.
- Stops: Sí.
- Valores predeterminados:
TenkanPeriod= 9KijunPeriod= 26SenkouSpanBPeriod= 52LookbackPeriod= 20DeviationMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Mean Reversion
- Dirección: Ambos
- Indicadores: Ichimoku
- 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.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Ichimoku cloud width mean reversion strategy.
/// Trades contractions and expansions of the Ichimoku cloud width around its recent average.
/// </summary>
public class IchimokuCloudWidthMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
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 Ichimoku _ichimoku;
private decimal[] _cloudWidthHistory;
private int _currentIndex;
private int _filledCount;
private int _cooldown;
/// <summary>
/// Tenkan-sen period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B period.
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Lookback period for cloud width statistics.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Deviation multiplier for mean reversion detection.
/// </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.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="IchimokuCloudWidthMeanReversionStrategy"/>.
/// </summary>
public IchimokuCloudWidthMeanReversionStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Tenkan-sen period", "Ichimoku")
.SetOptimize(5, 20, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Kijun-sen period", "Ichimoku")
.SetOptimize(20, 40, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Senkou Span B period", "Ichimoku")
.SetOptimize(40, 80, 4);
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Lookback period for cloud width statistics", "Strategy Parameters")
.SetOptimize(10, 50, 5);
_deviationMultiplier = Param(nameof(DeviationMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Deviation multiplier for mean reversion detection", "Strategy Parameters")
.SetOptimize(1m, 3m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage", "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", "Candle type for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ichimoku = null;
_currentIndex = default;
_filledCount = default;
_cooldown = default;
_cloudWidthHistory = new decimal[LookbackPeriod];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod },
};
_cloudWidthHistory = new decimal[LookbackPeriod];
_currentIndex = 0;
_filledCount = 0;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_ichimoku, ProcessIchimoku)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ichimoku);
DrawOwnTrades(area);
}
StartProtection(new(), new Unit(StopLossPercent, UnitTypes.Percent));
}
private void ProcessIchimoku(ICandleMessage candle, IIndicatorValue ichimokuValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_ichimoku.IsFormed)
return;
var typedValue = (IchimokuValue)ichimokuValue;
if (typedValue.SenkouA is not decimal senkouA ||
typedValue.SenkouB is not decimal senkouB)
return;
var cloudWidth = Math.Abs(senkouA - senkouB);
_cloudWidthHistory[_currentIndex] = cloudWidth;
_currentIndex = (_currentIndex + 1) % LookbackPeriod;
if (_filledCount < LookbackPeriod)
_filledCount++;
if (_filledCount < LookbackPeriod)
return;
var avgWidth = 0m;
var sumSq = 0m;
for (var i = 0; i < LookbackPeriod; i++)
avgWidth += _cloudWidthHistory[i];
avgWidth /= LookbackPeriod;
for (var i = 0; i < LookbackPeriod; i++)
{
var diff = _cloudWidthHistory[i] - avgWidth;
sumSq += diff * diff;
}
var stdWidth = (decimal)Math.Sqrt((double)(sumSq / LookbackPeriod));
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var narrowThreshold = avgWidth - stdWidth * DeviationMultiplier;
var wideThreshold = avgWidth + stdWidth * DeviationMultiplier;
var upperCloud = Math.Max(senkouA, senkouB);
var lowerCloud = Math.Min(senkouA, senkouB);
var priceAboveCloud = candle.ClosePrice > upperCloud;
var priceBelowCloud = candle.ClosePrice < lowerCloud;
if (Position == 0)
{
if (cloudWidth < narrowThreshold)
{
if (priceAboveCloud)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (priceBelowCloud)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (cloudWidth > wideThreshold)
{
if (priceBelowCloud)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (priceAboveCloud)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
else if (Position > 0 && cloudWidth >= avgWidth)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && cloudWidth <= avgWidth)
{
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.Indicators import Ichimoku
from StockSharp.Algo.Strategies import Strategy
class ichimoku_cloud_width_mean_reversion_strategy(Strategy):
"""
Ichimoku cloud width mean reversion strategy.
Trades contractions and expansions of the Ichimoku cloud width around its recent average.
"""
def __init__(self):
super(ichimoku_cloud_width_mean_reversion_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan Period", "Tenkan-sen period", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun Period", "Kijun-sen period", "Ichimoku")
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Senkou Span B period", "Ichimoku")
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Lookback period for cloud width statistics", "Strategy Parameters")
self._deviation_multiplier = self.Param("DeviationMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Deviation Multiplier", "Deviation multiplier for mean reversion detection", "Strategy Parameters")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss percentage", "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", "Candle type for strategy", "General")
self._ichimoku = None
self._cloud_width_history = None
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ichimoku_cloud_width_mean_reversion_strategy, self).OnReseted()
self._ichimoku = None
lb = int(self._lookback_period.Value)
self._cloud_width_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
def OnStarted2(self, time):
super(ichimoku_cloud_width_mean_reversion_strategy, self).OnStarted2(time)
lb = int(self._lookback_period.Value)
self._cloud_width_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._ichimoku = Ichimoku()
self._ichimoku.Tenkan.Length = int(self._tenkan_period.Value)
self._ichimoku.Kijun.Length = int(self._kijun_period.Value)
self._ichimoku.SenkouB.Length = int(self._senkou_span_b_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._ichimoku, self._process_ichimoku).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ichimoku)
self.DrawOwnTrades(area)
self.StartProtection(Unit(), Unit(self._stop_loss_percent.Value, UnitTypes.Percent))
def _process_ichimoku(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
if not self._ichimoku.IsFormed:
return
senkou_a = ichimoku_value.SenkouA
senkou_b = ichimoku_value.SenkouB
if senkou_a is None or senkou_b is None:
return
senkou_a_val = float(senkou_a)
senkou_b_val = float(senkou_b)
cloud_width = Math.Abs(senkou_a_val - senkou_b_val)
lb = int(self._lookback_period.Value)
self._cloud_width_history[self._current_index] = cloud_width
self._current_index = (self._current_index + 1) % lb
if self._filled_count < lb:
self._filled_count += 1
if self._filled_count < lb:
return
avg_width = 0.0
for i in range(lb):
avg_width += self._cloud_width_history[i]
avg_width /= float(lb)
sum_sq = 0.0
for i in range(lb):
diff = self._cloud_width_history[i] - avg_width
sum_sq += diff * diff
std_width = 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)
narrow_threshold = avg_width - std_width * dm
wide_threshold = avg_width + std_width * dm
upper_cloud = max(senkou_a_val, senkou_b_val)
lower_cloud = min(senkou_a_val, senkou_b_val)
close_price = float(candle.ClosePrice)
price_above_cloud = close_price > upper_cloud
price_below_cloud = close_price < lower_cloud
if self.Position == 0:
if cloud_width < narrow_threshold:
if price_above_cloud:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif price_below_cloud:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif cloud_width > wide_threshold:
if price_below_cloud:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif price_above_cloud:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position > 0 and cloud_width >= avg_width:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position < 0 and cloud_width <= avg_width:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
def CreateClone(self):
return ichimoku_cloud_width_mean_reversion_strategy()