Ver en GitHub

Estrategia Triple Top Triple Bottom

La estrategia Triple Top Triple Bottom es un port del asesor experto de MetaTrader con el mismo nombre. El sistema original combina varias capas de confirmación (dirección de tendencia, fuerza de momentum y filtro MACD) antes de entrar al mercado. Esta implementación StockSharp conserva las mismas ideas centrales y expone los umbrales importantes como parámetros de estrategia.

Lógica principal

  1. Filtro de tendencia: dos medias móviles ponderadas lineales (LWMA) calculadas sobre el precio típico (H+L+C)/3 definen la dirección de negociación. La LWMA rápida debe estar por encima de la lenta para permitir largos y por debajo para permitir cortos.
  2. Confirmación de momentum: el indicador momentum integrado, con longitud de retrospección configurable, debe desviarse del nivel neutral 100 al menos por el umbral definido por el usuario dentro de las tres últimas velas completadas. El EA requería el mismo comportamiento al analizar valores previos de momentum, y esta validación se replica para evitar entradas en mercados planos.
  3. Filtro MACD: un filtro clásico de línea de señal MACD 12/26/9 evita operar contra una tendencia fuerte. La estrategia solo compra cuando la línea MACD está por encima de la señal y vende cuando está por debajo.
  4. Gestión de riesgo: las órdenes de mercado se protegen con objetivos de stop-loss y take-profit medidos en unidades absolutas de precio. Los parámetros son opcionales; ponerlos en cero desactiva la orden respectiva. El código también cierra la posición si el umbral de riesgo opuesto se alcanza durante el procesamiento de velas.

Parámetros

  • Entry Candle: DataType que define el marco temporal de las velas de trabajo.
  • Fast LWMA / Slow LWMA: longitudes de los filtros de tendencia rápido y lento.
  • Momentum Period / Momentum Threshold: retrospección del indicador momentum y desviación mínima desde 100 que confirma una idea de operación.
  • Stop Loss / Take Profit: distancias protectoras en unidades absolutas de precio; también se envían como órdenes protectoras nativas mediante SetStopLoss y SetTakeProfit para que el control de riesgo se aplique incluso si la sesión de estrategia se detiene.

Diferencias frente a la versión MQL

  • Todos los extras de gestión monetaria (multiplicadores de lote, protección de patrimonio, trailing por vela, break-even y comprobaciones manuales de líneas de tendencia) se omitieron porque la API de alto nivel de StockSharp ya ofrece utilidades de dimensionamiento de posición y porque los objetos gráficos usados en el EA original son específicos de MetaTrader.
  • Los umbrales de riesgo se expresan en unidades absolutas de precio en lugar de pips. Esto mantiene la implementación neutral respecto al broker; los usuarios pueden convertir fácilmente su distancia de pip preferida multiplicando el tamaño de pip del broker por el número deseado de pips.
  • La salida gráfica usa áreas de StockSharp para velas de precio, medias móviles, momentum e indicadores MACD.

Notas de uso

  1. Adjunte la estrategia a un instrumento y configure el tipo de vela deseado antes de iniciar.
  2. Ajuste el umbral de momentum y las distancias de stop según la volatilidad del instrumento.
  3. La estrategia opera una sola posición neta. Cuando aparece una señal opuesta, la exposición actual se cierra primero, evitando operaciones superpuestas.

El código está completamente comentado en inglés y sigue las directrices de la API StockSharp de alto nivel proporcionadas en el repositorio.

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;

public class TripleTopTripleBottomStrategy : 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 TripleTopTripleBottomStrategy()
	{
		_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;
	}
}