CCI Slope Estrategia de Reversión a la Media
La estrategia CCI Slope de Reversión a la Media se centra en lecturas extremas del CCI para aprovechar la reversión. Las desviaciones amplias del nivel típico 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 CCI regresa al equilibrio. Parámetro inicial CciPeriod = 20.
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:
CciPeriod= 20SlopeLookback= 20ThresholdMultiplier= 2mStopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Reversión a la media
- Dirección: Ambos
- Indicadores: CCI
- 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>
/// CCI slope mean reversion strategy.
/// Trades reversions from extreme CCI slopes and exits when the slope returns to its recent average.
/// </summary>
public class CciSlopeMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _slopeLookback;
private readonly StrategyParam<decimal> _thresholdMultiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _entryCciMagnitude;
private readonly StrategyParam<DataType> _candleType;
private CommodityChannelIndex _cci;
private decimal _previousCciValue;
private decimal[] _slopeHistory;
private int _currentIndex;
private int _filledCount;
private int _cooldown;
private bool _isInitialized;
/// <summary>
/// CCI period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Period for slope statistics.
/// </summary>
public int SlopeLookback
{
get => _slopeLookback.Value;
set => _slopeLookback.Value = value;
}
/// <summary>
/// Standard deviation multiplier for entry threshold.
/// </summary>
public decimal ThresholdMultiplier
{
get => _thresholdMultiplier.Value;
set => _thresholdMultiplier.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>
/// Minimum absolute CCI value required for entries.
/// </summary>
public decimal EntryCciMagnitude
{
get => _entryCciMagnitude.Value;
set => _entryCciMagnitude.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="CciSlopeMeanReversionStrategy"/>.
/// </summary>
public CciSlopeMeanReversionStrategy()
{
_cciPeriod = Param(nameof(CciPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Commodity Channel Index period", "CCI Settings")
.SetOptimize(10, 40, 5);
_slopeLookback = Param(nameof(SlopeLookback), 20)
.SetGreaterThanZero()
.SetDisplay("Slope Lookback", "Period for slope statistics", "Slope Settings")
.SetOptimize(10, 50, 5);
_thresholdMultiplier = Param(nameof(ThresholdMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Threshold Multiplier", "Standard deviation multiplier for entry threshold", "Slope Settings")
.SetOptimize(1m, 3m, 0.5m);
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management");
_cooldownBars = Param(nameof(CooldownBars), 1200)
.SetRange(1, 5000)
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management");
_entryCciMagnitude = Param(nameof(EntryCciMagnitude), 100m)
.SetGreaterThanZero()
.SetDisplay("Entry CCI Magnitude", "Minimum absolute CCI value required for entries", "Signal Filters");
_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();
_cci = null;
_previousCciValue = default;
_slopeHistory = new decimal[SlopeLookback];
_currentIndex = default;
_filledCount = default;
_cooldown = default;
_isInitialized = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_cci = new CommodityChannelIndex { Length = CciPeriod };
_slopeHistory = new decimal[SlopeLookback];
_currentIndex = 0;
_filledCount = 0;
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_cci, ProcessCandle)
.Start();
StartProtection(new(), new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _cci);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal cciValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_cci.IsFormed)
return;
if (!_isInitialized)
{
_previousCciValue = cciValue;
_isInitialized = true;
return;
}
var slope = cciValue - _previousCciValue;
_previousCciValue = cciValue;
_slopeHistory[_currentIndex] = slope;
_currentIndex = (_currentIndex + 1) % SlopeLookback;
if (_filledCount < SlopeLookback)
_filledCount++;
if (_filledCount < SlopeLookback)
return;
var averageSlope = 0m;
var sumSq = 0m;
for (var i = 0; i < SlopeLookback; i++)
averageSlope += _slopeHistory[i];
averageSlope /= SlopeLookback;
for (var i = 0; i < SlopeLookback; i++)
{
var diff = _slopeHistory[i] - averageSlope;
sumSq += diff * diff;
}
var slopeStdDev = (decimal)Math.Sqrt((double)(sumSq / SlopeLookback));
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var lowerThreshold = averageSlope - ThresholdMultiplier * slopeStdDev;
var upperThreshold = averageSlope + ThresholdMultiplier * slopeStdDev;
if (Position == 0)
{
if (slope < lowerThreshold && cciValue <= -EntryCciMagnitude)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (slope > upperThreshold && cciValue >= EntryCciMagnitude)
{
SellMarket();
_cooldown = CooldownBars;
}
}
else if (Position > 0 && slope >= averageSlope)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
else if (Position < 0 && slope <= 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.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class cci_slope_mean_reversion_strategy(Strategy):
"""
CCI slope mean reversion strategy.
Trades reversions from extreme CCI slopes and exits when the slope returns to its recent average.
"""
def __init__(self):
super(cci_slope_mean_reversion_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("CCI Period", "Commodity Channel Index period", "CCI Settings")
self._slope_lookback = self.Param("SlopeLookback", 20) \
.SetGreaterThanZero() \
.SetDisplay("Slope Lookback", "Period for slope statistics", "Slope Settings")
self._threshold_multiplier = self.Param("ThresholdMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("Threshold Multiplier", "Standard deviation multiplier for entry threshold", "Slope Settings")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Risk Management")
self._cooldown_bars = self.Param("CooldownBars", 1200) \
.SetDisplay("Cooldown Bars", "Bars to wait between orders", "Risk Management")
self._entry_cci_magnitude = self.Param("EntryCciMagnitude", 100.0) \
.SetGreaterThanZero() \
.SetDisplay("Entry CCI Magnitude", "Minimum absolute CCI value required for entries", "Signal Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cci = None
self._previous_cci_value = 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(cci_slope_mean_reversion_strategy, self).OnReseted()
self._cci = None
self._previous_cci_value = 0.0
lb = int(self._slope_lookback.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(cci_slope_mean_reversion_strategy, self).OnStarted2(time)
lb = int(self._slope_lookback.Value)
self._slope_history = [0.0] * lb
self._current_index = 0
self._filled_count = 0
self._cooldown = 0
self._cci = CommodityChannelIndex()
self._cci.Length = int(self._cci_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._cci, self._process_candle).Start()
self.StartProtection(Unit(), Unit(self._stop_loss_percent.Value, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._cci)
self.DrawOwnTrades(area)
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if not self._cci.IsFormed:
return
cv = float(cci_value)
if not self._is_initialized:
self._previous_cci_value = cv
self._is_initialized = True
return
slope = cv - self._previous_cci_value
self._previous_cci_value = cv
lb = int(self._slope_lookback.Value)
self._slope_history[self._current_index] = 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
tm = float(self._threshold_multiplier.Value)
lower_threshold = avg_slope - tm * std_slope
upper_threshold = avg_slope + tm * std_slope
entry_mag = float(self._entry_cci_magnitude.Value)
if self.Position == 0:
if slope < lower_threshold and cv <= -entry_mag:
self.BuyMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif slope > upper_threshold and cv >= entry_mag:
self.SellMarket()
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position > 0 and slope >= avg_slope:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
elif self.Position < 0 and slope <= avg_slope:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = int(self._cooldown_bars.Value)
def CreateClone(self):
return cci_slope_mean_reversion_strategy()