Ver en GitHub

Estrategia Exp Color TSI Oscilador

Visión general

  • Conversión del asesor experto de MetaTrader 5 Exp_ColorTSI-Oscillator al framework StockSharp.
  • Reconstruye el oscilador ColorTSI: un True Strength Index de doble suavizado con una línea de trigger retrasada y múltiples algoritmos de suavizado tomados de SmoothAlgorithms.mqh.
  • Genera trades cuando el oscilador sube o baja con respecto a su trigger retrasado, replicando el estilo de "reversión de oscilación" utilizado por el EA original.

Reconstrucción del indicador

  • El precio aplicado se selecciona a través de la opción ColorTsiAppliedPrice (cierre, apertura, mediana, típico, ponderado, Demark, etc.).
  • El momentum de precio (diff = price[n] - price[n-1]) y su valor absoluto se suavizan en dos etapas:
    1. Primera etapa: ColorTsiSmoothingMethod configurable (Sma, Ema, Smma, Lwma, Jjma, Jurx, Parma, T3, Vidya, Ama) con longitud FirstLength y fase FirstPhase para filtros tipo Jurik.
    2. Segunda etapa: opciones de método idénticas con SecondLength/SecondPhase aplicadas a la serie de momentum ya suavizada.
  • La salida del oscilador es TSI = 100 * smoothMomentum / smoothAbsMomentum. Cuando el denominador es cero, el valor se ignora.
  • La línea de trigger se obtiene retrasando el TSI por TriggerShift barras, reflejando la lógica del buffer de MetaTrader.
  • Los valores históricos se almacenan para que SignalBar coincida con el patrón de acceso CopyBuffer de MetaTrader (índice SignalBar = barra cerrada más reciente examinada, SignalBar + 1 = barra anterior, etc.).

Reglas de trading

  • Los cálculos se ejecutan en velas terminadas suministradas por CandleType (por defecto: marco temporal de 4 horas).
  • Sea TSI[k] el valor del oscilador y Trigger[k] la serie retrasada.
  • Contexto alcista: TSI[SignalBar + 1] > Trigger[SignalBar + 1] ⇒ la barra anterior mostró momentum alcista.
    • Cerrar cortos si EnableShortExits es true.
    • Abrir una posición larga cuando EnableLongEntries es true y TSI[SignalBar] ≤ Trigger[SignalBar], señalando un movimiento alcista después del retroceso.
  • Contexto bajista: TSI[SignalBar + 1] < Trigger[SignalBar + 1] ⇒ la barra anterior mostró momentum bajista.
    • Cerrar largos si EnableLongExits es true.
    • Abrir una posición corta cuando EnableShortEntries es true y TSI[SignalBar] ≥ Trigger[SignalBar].
  • Las señales de entrada se codifican por el tiempo de la barra analizada más un marco temporal completo; cada señal puede activar como máximo un trade gracias a las guardas _lastLongEntryTime / _lastShortEntryTime.
  • Todas las acciones se ejecutan con órdenes de mercado. Las posiciones opuestas existentes se cierran antes de las reversiones.

Parámetros

Parámetro Descripción Por defecto
CandleType Stream de datos usado para el análisis. Soporta cualquier DataType (velas de tiempo, tick, volumen). Marco temporal H4
Volume Tamaño de orden fijo que reemplaza los bloques de gestión monetaria del EA. Debe ser > 0. 0.1
FirstMethod, FirstLength, FirstPhase Primera etapa de suavizado para momentum y momentum absoluto. SMA, 12, 15
SecondMethod, SecondLength, SecondPhase Segunda etapa de suavizado. SMA, 12, 15
PriceMode Opción de precio aplicado que alimenta el oscilador. Close
SignalBar Desplazamiento de barra usado para evaluar señales (1 = última barra cerrada). 1
TriggerShift Retraso aplicado a la línea de trigger (1 reproduce el indicador original). 1
EnableLongEntries / EnableShortEntries Permitir apertura de trades largos/cortos. true
EnableLongExits / EnableShortExits Permitir cerrar posiciones en contexto opuesto. true
StopLossPoints Distancia de stop-loss en puntos de precio (convertida con el PriceStep del instrumento). 1000
TakeProfitPoints Distancia de take-profit en puntos de precio. 2000

Gestión de riesgo

  • El EA original dependía de funciones auxiliares de TradeAlgorithms.mqh para la colocación de SL/TP. La versión C# llama a StartProtection con las distancias seleccionadas convertidas a UnitTypes.Point.
  • Si cualquier distancia se establece en 0, la orden de protección correspondiente se omite.
  • No se implementan trailing stops ni escalonamiento de posición; estos coinciden con el comportamiento de MetaTrader para este asesor.

Diferencias con la versión MetaTrader

  • El sizing de lote basado en margen (MM y MMMode) se reemplaza por un parámetro Volume fijo. Esto mantiene el comportamiento determinista entre brokers y evita replicar la lógica de apalancamiento específica de cada cuenta.
  • El deslizamiento (Deviation_) no se emula porque las órdenes de mercado de StockSharp no exponen un parámetro de deslizamiento.
  • El suavizado del indicador se reconstruye completamente usando indicadores de StockSharp (incluido el manejo de fase Jurik a través de reflexión), por lo que los valores de señal son consistentes con los buffers originales.
  • La implementación de Python se omite intencionalmente según lo solicitado.

Notas de uso

  • Asegurarse de que el instrumento seleccionado proporcione el tipo de vela solicitado por CandleType. Para marcos temporales estándar usar TimeSpan.FromHours(x).TimeFrame().
  • SignalBar debe ser ≥ TriggerShift para obtener valores de trigger válidos; de lo contrario, las señales se omiten hasta que se acumule suficiente historial.
  • Como la estrategia reacciona en velas terminadas, habilitar el registro de órdenes en tiempo real solo después de que IsFormedAndOnlineAndAllowTrading() se vuelva true.
  • El área de gráfico visualiza velas de precio y trades ejecutados; los indicadores se reconstruyen internamente y no se trazan automáticamente.
  • Para reproducir los valores por defecto de MetaTrader: mantener todos los ajustes de suavizado en SMA con longitud 12, mantener ambos toggles de entrada y salida habilitados, y usar las distancias de stop/take por defecto.
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>
/// Color TSI Oscillator strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class ExpColorTsiOscillatorStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ExpColorTsiOscillatorStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}