Estrategia de Indicador de Volumen de Ticks Ergodic
Esta estrategia aplica el True Strength Index (TSI) a los datos de velas y lo compara con una línea de señal de media móvil exponencial. Se abre una posición larga cuando el TSI cruza por encima de la línea de señal, mientras que se abre una posición corta cuando cruza por debajo.
Parámetros
Candle Type – marco temporal de las velas usadas para los cálculos.
Short Length – período de suavizado rápido del TSI.
Long Length – período de suavizado lento del TSI.
Signal Length – período de la EMA utilizada como línea de señal.
Lógica
Suscribirse a las velas del marco temporal seleccionado.
Calcular el TSI para cada vela finalizada.
Procesar el TSI a través de una EMA para obtener una línea de señal.
Cuando el TSI cruza por encima de la línea de señal, entrar largo (cerrando cualquier posición corta).
Cuando el TSI cruza por debajo de la línea de señal, entrar corto (cerrando cualquier posición larga).
La estrategia es una adaptación del ejemplo MQL "exp_ergodic_ticks_volume_indicator.mq5" y utiliza únicamente indicadores integrados de StockSharp.
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>
/// Strategy based on True Strength Index crossovers of the signal line.
/// </summary>
public class ErgodicTicksVolumeIndicatorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _firstLength;
private readonly StrategyParam<int> _secondLength;
private readonly StrategyParam<int> _signalLength;
private decimal _prevTsi;
private decimal _prevSignal;
private bool _prevReady;
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 ErgodicTicksVolumeIndicatorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_firstLength = Param(nameof(FirstLength), 25)
.SetGreaterThanZero()
.SetDisplay("First Length", "First smoothing length", "Indicator");
_secondLength = Param(nameof(SecondLength), 13)
.SetGreaterThanZero()
.SetDisplay("Second Length", "Second smoothing length", "Indicator");
_signalLength = Param(nameof(SignalLength), 7)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Signal line length", "Indicator");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevTsi = default;
_prevSignal = default;
_prevReady = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var tsi = new TrueStrengthIndex
{
FirstLength = FirstLength,
SecondLength = SecondLength,
SignalLength = SignalLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(tsi, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
var tsiVal = (ITrueStrengthIndexValue)value;
if (tsiVal.Tsi is not decimal tsi || tsiVal.Signal is not decimal signal)
return;
if (!_prevReady)
{
_prevTsi = tsi;
_prevSignal = signal;
_prevReady = true;
return;
}
// TSI crosses above signal - buy
if (_prevTsi <= _prevSignal && tsi > signal && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// TSI crosses below signal - sell
else if (_prevTsi >= _prevSignal && tsi < signal && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
}
_prevTsi = tsi;
_prevSignal = signal;
}
}
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 TrueStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class ergodic_ticks_volume_indicator_strategy(Strategy):
def __init__(self):
super(ergodic_ticks_volume_indicator_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._first_length = self.Param("FirstLength", 25) \
.SetDisplay("First Length", "First smoothing length", "Indicator")
self._second_length = self.Param("SecondLength", 13) \
.SetDisplay("Second Length", "Second smoothing length", "Indicator")
self._signal_length = self.Param("SignalLength", 7) \
.SetDisplay("Signal Length", "Signal line length", "Indicator")
self._prev_tsi = 0.0
self._prev_signal = 0.0
self._prev_ready = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FirstLength(self):
return self._first_length.Value
@FirstLength.setter
def FirstLength(self, value):
self._first_length.Value = value
@property
def SecondLength(self):
return self._second_length.Value
@SecondLength.setter
def SecondLength(self, value):
self._second_length.Value = value
@property
def SignalLength(self):
return self._signal_length.Value
@SignalLength.setter
def SignalLength(self, value):
self._signal_length.Value = value
def OnStarted2(self, time):
super(ergodic_ticks_volume_indicator_strategy, self).OnStarted2(time)
tsi = TrueStrengthIndex()
tsi.FirstLength = self.FirstLength
tsi.SecondLength = self.SecondLength
tsi.SignalLength = self.SignalLength
self.SubscribeCandles(self.CandleType) \
.BindEx(tsi, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, value):
if candle.State != CandleStates.Finished:
return
tsi_val = value.Tsi
signal_val = value.Signal
if tsi_val is None or signal_val is None:
return
tsi = float(tsi_val)
signal = float(signal_val)
if not self._prev_ready:
self._prev_tsi = tsi
self._prev_signal = signal
self._prev_ready = True
return
if self._prev_tsi <= self._prev_signal and tsi > signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_tsi >= self._prev_signal and tsi < signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_tsi = tsi
self._prev_signal = signal
def OnReseted(self):
super(ergodic_ticks_volume_indicator_strategy, self).OnReseted()
self._prev_tsi = 0.0
self._prev_signal = 0.0
self._prev_ready = False
def CreateClone(self):
return ergodic_ticks_volume_indicator_strategy()