Estrategia SMI Ergódica Adaptativa
La estrategia SMI Ergódica Adaptativa utiliza el oscilador True Strength Index (TSI) con una línea de señal EMA para detectar reversiones desde extremos de sobrecompra o sobreventa. Se abre una posición larga cuando el TSI cruza por encima del umbral de sobreventa mientras se mantiene por encima de su línea de señal. Se abre una posición corta cuando el TSI cruza por debajo del umbral de sobrecompra y está por debajo de la línea de señal.
Detalles
- Criterios de entrada:
- TSI cruza por encima de sobreventa y TSI > señal (largo).
- TSI cruza por debajo de sobrecompra y TSI < señal (corto).
- Largo/Corto: Ambos.
- Criterios de salida:
- La señal inversa activa la operación opuesta.
- Stops: Ninguno.
- Valores predeterminados:
LongLength= 12ShortLength= 5SignalLength= 5OversoldThreshold= -0.4OverboughtThreshold= 0.4
- Filtros:
- Categoría: Oscilador de momentum
- Dirección: Largo/Corto
- Indicadores: True Strength Index, EMA
- Stops: No
- Complejidad: Bajo
- Marco temporal: Cualquiera
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Bajo
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Adaptive SMI Ergodic Strategy - uses True Strength Index crossovers with signal line confirmation.
/// </summary>
public class AdaptiveSmiErgodicStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _firstLength;
private readonly StrategyParam<int> _secondLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<decimal> _oversoldThreshold;
private readonly StrategyParam<decimal> _overboughtThreshold;
private readonly StrategyParam<int> _cooldownBars;
private decimal _previousTsi;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FirstLength { get => _firstLength.Value; set => _firstLength.Value = value; }
public int SecondLength { get => _secondLength.Value; set => _secondLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public decimal OversoldThreshold { get => _oversoldThreshold.Value; set => _oversoldThreshold.Value = value; }
public decimal OverboughtThreshold { get => _overboughtThreshold.Value; set => _overboughtThreshold.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public AdaptiveSmiErgodicStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_firstLength = Param(nameof(FirstLength), 25)
.SetGreaterThanZero()
.SetDisplay("First Length", "First smoothing length for TSI", "TSI")
.SetOptimize(10, 30, 5);
_secondLength = Param(nameof(SecondLength), 13)
.SetGreaterThanZero()
.SetDisplay("Second Length", "Second smoothing length for TSI", "TSI")
.SetOptimize(5, 20, 3);
_signalLength = Param(nameof(SignalLength), 7)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Signal EMA length", "TSI")
.SetOptimize(3, 15, 2);
_oversoldThreshold = Param(nameof(OversoldThreshold), -10m)
.SetDisplay("Oversold Threshold", "Oversold level for TSI", "TSI");
_overboughtThreshold = Param(nameof(OverboughtThreshold), 10m)
.SetDisplay("Overbought Threshold", "Overbought level for TSI", "TSI");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousTsi = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousTsi = 0;
var tsi = new TrueStrengthIndex
{
FirstLength = FirstLength,
SecondLength = SecondLength,
SignalLength = SignalLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(tsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, tsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue tsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var tv = (ITrueStrengthIndexValue)tsiValue;
if (tv.Tsi is not decimal tsiVal || tv.Signal is not decimal signalVal)
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_previousTsi = tsiVal;
return;
}
var crossAboveOversold = _previousTsi <= OversoldThreshold && tsiVal > OversoldThreshold;
var crossBelowOverbought = _previousTsi >= OverboughtThreshold && tsiVal < OverboughtThreshold;
if (crossAboveOversold && tsiVal > signalVal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (crossBelowOverbought && tsiVal < signalVal && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_previousTsi = tsiVal;
}
}
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
from StockSharp.Algo.Indicators import TrueStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class adaptive_smi_ergodic_strategy(Strategy):
def __init__(self):
super(adaptive_smi_ergodic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._first_length = self.Param("FirstLength", 25) \
.SetGreaterThanZero() \
.SetDisplay("First Length", "First smoothing length for TSI", "TSI")
self._second_length = self.Param("SecondLength", 13) \
.SetGreaterThanZero() \
.SetDisplay("Second Length", "Second smoothing length for TSI", "TSI")
self._signal_length = self.Param("SignalLength", 7) \
.SetGreaterThanZero() \
.SetDisplay("Signal Length", "Signal EMA length", "TSI")
self._oversold_threshold = self.Param("OversoldThreshold", -10.0) \
.SetDisplay("Oversold Threshold", "Oversold level for TSI", "TSI")
self._overbought_threshold = self.Param("OverboughtThreshold", 10.0) \
.SetDisplay("Overbought Threshold", "Overbought level for TSI", "TSI")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._previous_tsi = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adaptive_smi_ergodic_strategy, self).OnReseted()
self._previous_tsi = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adaptive_smi_ergodic_strategy, self).OnStarted2(time)
self._previous_tsi = 0.0
tsi = TrueStrengthIndex()
tsi.FirstLength = int(self._first_length.Value)
tsi.SecondLength = int(self._second_length.Value)
tsi.SignalLength = int(self._signal_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(tsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, tsi)
self.DrawOwnTrades(area)
def _on_process(self, candle, tsi_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if tsi_value.Tsi is None or tsi_value.Signal is None:
return
tsi_val = float(tsi_value.Tsi)
signal_val = float(tsi_value.Signal)
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._previous_tsi = tsi_val
return
oversold = float(self._oversold_threshold.Value)
overbought = float(self._overbought_threshold.Value)
cooldown = int(self._cooldown_bars.Value)
cross_above_oversold = self._previous_tsi <= oversold and tsi_val > oversold
cross_below_overbought = self._previous_tsi >= overbought and tsi_val < overbought
if cross_above_oversold and tsi_val > signal_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif cross_below_overbought and tsi_val < signal_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._previous_tsi = tsi_val
def CreateClone(self):
return adaptive_smi_ergodic_strategy()