Ver en GitHub

Estrategia UP3x1 Premium

La Estrategia UP3x1 Premium es un port en C# del asesor experto de MetaTrader up3x1_premium_v2M. Combina cruces de EMA rápida/lenta con filtros de velas de gran rango y un filtro de contexto diario para capturar rupturas de impulso mientras mantiene el riesgo gestionado a través de objetivos fijos y trailing stops.

Cómo Funciona

  1. Detección de Tendencia

    • Calcula dos EMAs en el marco temporal de trabajo (por defecto períodos 12 y 26).
    • Rastrea los dos valores EMA anteriores para identificar cruces alcistas o bajistas similar a la lógica MQL.
    • Mantiene una EMA diaria para entender el sesgo más amplio.
  2. Lógica de Entrada

    • Los setups largos se activan cuando ocurre cualquiera de los siguientes:
      • La EMA rápida cruza por encima de la EMA lenta y las dos aperturas de velas anteriores muestran progresión ascendente.
      • La vela anterior forma una barra alcista de gran rango cuyo cuerpo supera el umbral de cuerpo configurado.
      • A medianoche, si la vela diaria anterior cerró notablemente más bajo de su apertura (capitulación), se permite una señal de rebote.
      • El precio opera por encima de la EMA diaria actual, favoreciendo el lado largo.
    • Los setups cortos se activan cuando se cumplen las condiciones espejo (cruce EMA bajista, barra bajista de gran rango, o reversión de medianoche en la dirección opuesta).
    • Cuando se activan simultáneamente los disparadores largos y cortos, la estrategia sigue la relación EMA prevaleciente para desempatar.
  3. Gestión de Salida

    • Una posición abierta se cierra cuando:
      • Las EMAs convergen dentro de ±0.1%, señalizando pérdida de convicción direccional.
      • El precio toca los offsets de take-profit o stop-loss definidos en unidades de precio absolutas.
      • El trailing stop (si está habilitado) es arrastrado detrás del precio y posteriormente golpeado.
  4. Manejo de Posición

    • Las operaciones se abren solo cuando la estrategia está plana, coincidiendo con el comportamiento original del EA.
    • El volumen se controla mediante el parámetro OrderVolume y se aplica a cada orden de mercado.

Parámetros

Parámetro Descripción
OrderVolume Tamaño de orden en lotes/contratos para cada operación.
FastEmaLength / SlowEmaLength Períodos para las EMAs rápida y lenta en el marco temporal de trabajo.
DailyEmaLength Período para la EMA calculada en las velas diarias.
TakeProfit Objetivo de beneficio absoluto en unidades de precio (establecer en cero para deshabilitar).
StopLoss Distancia de stop absoluta en unidades de precio (establecer en cero para deshabilitar).
TrailingStop Distancia de trailing que sigue al precio una vez que el movimiento supera el umbral.
RangeThreshold Rango total mínimo que la vela anterior debe superar para calificar como barra de gran rango.
BodyThreshold Tamaño mínimo del cuerpo de la vela que define barras de impulso alcistas/bajistas.
DailyReversalThreshold Tamaño del reverso diario anterior requerido durante el filtro de medianoche.
CandleType Marco temporal de trabajo para la lógica principal de EMA y precio.
DailyCandleType Marco temporal superior usado para el contexto EMA diario.

Notas de Uso

  • Los valores predeterminados imitan las constantes numéricas encontradas en el EA original (convertidas de valores en puntos a offsets de precio decimal).
  • Ajuste los umbrales basados en precio (TakeProfit, StopLoss, TrailingStop, umbrales de rango/cuerpo) para que coincidan con el tamaño del tick del instrumento operado.
  • El filtro de EMA diario reemplaza el sesgo largo incondicional presente en el script MQL, manteniendo las operaciones alineadas con la tendencia del marco temporal superior prevaleciente.
  • Siempre realice backtesting en datos históricos y pruebas hacia adelante en un entorno de demo antes de habilitar el trading en vivo.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the UP3x1 Premium expert advisor that relies on EMA momentum with daily context.
/// </summary>
public class Up3x1PremiumStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _dailyEmaLength;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _rangeThreshold;
	private readonly StrategyParam<decimal> _bodyThreshold;
	private readonly StrategyParam<decimal> _dailyReversalThreshold;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DataType> _dailyCandleType;

	private decimal? _fastPrev;
	private decimal? _fastPrev2;
	private decimal? _slowPrev;
	private decimal? _slowPrev2;
	private ICandleMessage _prevCandle;
	private ICandleMessage _prevPrevCandle;

	private decimal? _dailyEmaValue;
	private decimal? _prevDailyOpen;
	private decimal? _prevDailyClose;

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takeProfitPrice;
	private decimal? _trailingStopPrice;

	public Up3x1PremiumStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Order Volume", "Volume for each trade", "Trading")
		;

		_fastEmaLength = Param(nameof(FastEmaLength), 12)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA Length", "Length of the fast EMA", "Indicators")
		;

		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA Length", "Length of the slow EMA", "Indicators")
		;

		_dailyEmaLength = Param(nameof(DailyEmaLength), 10)
		.SetGreaterThanZero()
		.SetDisplay("Daily EMA Length", "EMA length for the daily trend filter", "Indicators")
		;

		_takeProfit = Param(nameof(TakeProfit), 0.015m)
		.SetNotNegative()
		.SetDisplay("Take Profit", "Absolute take profit distance", "Risk")
		;

		_stopLoss = Param(nameof(StopLoss), 0.01m)
		.SetNotNegative()
		.SetDisplay("Stop Loss", "Absolute stop loss distance", "Risk")
		;

		_trailingStop = Param(nameof(TrailingStop), 0.001m)
		.SetNotNegative()
		.SetDisplay("Trailing Stop", "Distance for trailing stop updates", "Risk")
		;

		_rangeThreshold = Param(nameof(RangeThreshold), 0.006m)
		.SetNotNegative()
		.SetDisplay("Range Threshold", "Minimum candle range to qualify as wide", "Filters")
		;

		_bodyThreshold = Param(nameof(BodyThreshold), 0.005m)
		.SetNotNegative()
		.SetDisplay("Body Threshold", "Minimum candle body for momentum", "Filters")
		;

		_dailyReversalThreshold = Param(nameof(DailyReversalThreshold), 0.006m)
		.SetNotNegative()
		.SetDisplay("Daily Reversal Threshold", "Minimum prior day reversal size", "Filters")
		;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Primary working timeframe", "General");

		_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromDays(1).TimeFrame())
		.SetDisplay("Daily Candle Type", "Higher timeframe for daily context", "General");
	}

	/// <summary>
	/// Trade volume expressed in security lots.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Length of the fast EMA on the working timeframe.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Length of the slow EMA on the working timeframe.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// Length of the EMA used on the daily candles.
	/// </summary>
	public int DailyEmaLength
	{
		get => _dailyEmaLength.Value;
		set => _dailyEmaLength.Value = value;
	}

	/// <summary>
	/// Absolute take profit expressed in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Absolute stop loss expressed in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Distance used for trailing stop updates.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Minimum candle range that activates the momentum filter.
	/// </summary>
	public decimal RangeThreshold
	{
		get => _rangeThreshold.Value;
		set => _rangeThreshold.Value = value;
	}

	/// <summary>
	/// Minimum candle body needed to qualify as a thrust.
	/// </summary>
	public decimal BodyThreshold
	{
		get => _bodyThreshold.Value;
		set => _bodyThreshold.Value = value;
	}

	/// <summary>
	/// Size of the prior daily reversal required during the midnight check.
	/// </summary>
	public decimal DailyReversalThreshold
	{
		get => _dailyReversalThreshold.Value;
		set => _dailyReversalThreshold.Value = value;
	}

	/// <summary>
	/// Working timeframe for the main signals.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Higher timeframe used for the daily EMA filter.
	/// </summary>
	public DataType DailyCandleType
	{
		get => _dailyCandleType.Value;
		set => _dailyCandleType.Value = value;
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType), (Security, DailyCandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_fastPrev = null;
		_fastPrev2 = null;
		_slowPrev = null;
		_slowPrev2 = null;
		_prevCandle = null;
		_prevPrevCandle = null;

		_dailyEmaValue = null;
		_prevDailyOpen = null;
		_prevDailyClose = null;

		ClearTradeLevels();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		Volume = OrderVolume;

		// Create EMA indicators for the working timeframe.
		var fastEma = new EMA { Length = FastEmaLength };
		var slowEma = new EMA { Length = SlowEmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(fastEma, slowEma, ProcessCandle)
		.Start();

		// Daily subscription provides the higher timeframe confirmation.
		var dailyEma = new EMA { Length = DailyEmaLength };
		var dailySubscription = SubscribeCandles(DailyCandleType);
		dailySubscription
		.Bind(dailyEma, ProcessDailyCandle)
		.Start();

		StartProtection(null, null);

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessDailyCandle(ICandleMessage candle, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Store the latest completed daily information for intraday decisions.
		_dailyEmaValue = emaValue;
		_prevDailyOpen = candle.OpenPrice;
		_prevDailyClose = candle.ClosePrice;
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastEma, decimal slowEma)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Manage an existing position before looking for fresh entries.
		ManageOpenPosition(candle);

		var haveHistory = _prevCandle != null && _prevPrevCandle != null &&
		_fastPrev.HasValue && _fastPrev2.HasValue && _slowPrev.HasValue && _slowPrev2.HasValue;

		if (Position == 0m && haveHistory && IsFormedAndOnlineAndAllowTrading())
		{
			var bullishCross = _fastPrev2.Value < _slowPrev2.Value && _fastPrev.Value > _slowPrev.Value &&
			_prevPrevCandle.OpenPrice < _prevCandle.OpenPrice;

			var wideBullish = (_prevCandle.HighPrice - _prevCandle.LowPrice) > RangeThreshold &&
			_prevCandle.ClosePrice > _prevCandle.OpenPrice &&
			(_prevCandle.ClosePrice - _prevCandle.OpenPrice) > BodyThreshold;

			var midnight = candle.OpenTime.Hour == 0;
			var dailyBounce = midnight &&
			_prevDailyOpen is decimal dayOpen &&
			_prevDailyClose is decimal dayClose &&
			dayOpen > dayClose &&
			(dayOpen - dayClose) > DailyReversalThreshold;

			var priceAboveDaily = _dailyEmaValue is decimal daily && candle.ClosePrice >= daily;

			var longSignal = bullishCross || wideBullish || dailyBounce || priceAboveDaily;

			var bearishCross = _fastPrev2.Value > _slowPrev2.Value && _fastPrev.Value < _slowPrev.Value &&
			_prevPrevCandle.OpenPrice > _prevCandle.OpenPrice;

			var wideBearish = (_prevCandle.HighPrice - _prevCandle.LowPrice) > RangeThreshold &&
			_prevCandle.OpenPrice > _prevCandle.ClosePrice &&
			(_prevCandle.OpenPrice - _prevCandle.ClosePrice) > BodyThreshold;

			var midnightSell = midnight &&
			_prevDailyOpen is decimal dayOpenSell &&
			_prevDailyClose is decimal dayCloseSell &&
			dayOpenSell < dayCloseSell &&
			(dayCloseSell - dayOpenSell) > DailyReversalThreshold;

			var shortSignal = bearishCross || wideBearish || midnightSell;

			if (longSignal && shortSignal)
			{
				// Break ties with the latest EMA relationship.
				if (_fastPrev.Value >= _slowPrev.Value)
				shortSignal = false;
				else
				longSignal = false;
			}

			if (longSignal && OrderVolume > 0m)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = StopLoss > 0m ? _entryPrice - StopLoss : null;
				_takeProfitPrice = TakeProfit > 0m ? _entryPrice + TakeProfit : null;
				_trailingStopPrice = TrailingStop > 0m ? _entryPrice - TrailingStop : null;
			}
			else if (shortSignal && OrderVolume > 0m)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = StopLoss > 0m ? _entryPrice + StopLoss : null;
				_takeProfitPrice = TakeProfit > 0m ? _entryPrice - TakeProfit : null;
				_trailingStopPrice = TrailingStop > 0m ? _entryPrice + TrailingStop : null;
			}
		}

		// Preserve history to mimic the MQL index-based access pattern.
		_prevPrevCandle = _prevCandle;
		_prevCandle = candle;

		_fastPrev2 = _fastPrev;
		_fastPrev = fastEma;
		_slowPrev2 = _slowPrev;
		_slowPrev = slowEma;
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			UpdateTrailingStopForLong(candle);

			var exit = AreEmaNear(_fastPrev, _slowPrev);

			if (!exit && _takeProfitPrice is decimal tp && candle.HighPrice >= tp)
			exit = true;

			if (!exit && _stopPrice is decimal sl && candle.LowPrice <= sl)
			exit = true;

			if (!exit && _trailingStopPrice is decimal trail && candle.LowPrice <= trail)
			exit = true;

			if (exit)
			{
				SellMarket();
				ClearTradeLevels();
			}
		}
		else if (Position < 0m)
		{
			UpdateTrailingStopForShort(candle);

			var exit = AreEmaNear(_fastPrev, _slowPrev);

			if (!exit && _takeProfitPrice is decimal tp && candle.LowPrice <= tp)
			exit = true;

			if (!exit && _stopPrice is decimal sl && candle.HighPrice >= sl)
			exit = true;

			if (!exit && _trailingStopPrice is decimal trail && candle.HighPrice >= trail)
			exit = true;

			if (exit)
			{
				BuyMarket();
				ClearTradeLevels();
			}
		}
	}

	private void UpdateTrailingStopForLong(ICandleMessage candle)
	{
		if (TrailingStop <= 0m || _entryPrice is not decimal entry)
		return;

		var move = candle.HighPrice - entry;
		if (move < TrailingStop)
		return;

		var newStop = candle.HighPrice - TrailingStop;
		if (_trailingStopPrice is null || newStop > _trailingStopPrice)
		_trailingStopPrice = newStop;
	}

	private void UpdateTrailingStopForShort(ICandleMessage candle)
	{
		if (TrailingStop <= 0m || _entryPrice is not decimal entry)
		return;

		var move = entry - candle.LowPrice;
		if (move < TrailingStop)
		return;

		var newStop = candle.LowPrice + TrailingStop;
		if (_trailingStopPrice is null || newStop < _trailingStopPrice)
		_trailingStopPrice = newStop;
	}

	private static bool AreEmaNear(decimal? fast, decimal? slow)
	{
		if (fast is not decimal fastValue || slow is not decimal slowValue)
		return false;

		if (slowValue == 0m)
		return false;

		var diff = Math.Abs(fastValue - slowValue);
		return diff <= Math.Abs(slowValue) * 0.001m;
	}

	private void ClearTradeLevels()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takeProfitPrice = null;
		_trailingStopPrice = null;
	}
}