Ver en GitHub

Estrategia V1N1 Lonny Breakout

Descripción general

La estrategia V1N1 Lonny Breakout replica el asesor experto de MetaTrader "V1N1 LONNY". Apunta a rompimientos que emergen alrededor de las sesiones de Londres y Nueva York construyendo un rango de apertura y esperando un cierre decisivo fuera de ese rango. La estrategia se basa en una media móvil exponencial para capturar la tendencia predominante y en un oscilador estocástico para filtrar condiciones de sobrecompra o sobreventa antes de entrar al mercado.

Un modelo de riesgo configurable permite dimensionar posiciones por volumen fijo o como porcentaje del capital de la cuenta. La implementación también incluye filtrado de spread opcional, stops de trailing, y un tiempo de espera basado en barras que cierra la operación si el momentum se desvanece después de un número predefinido de velas.

Lógica de trading

  1. Alineación de sesión – El trading solo se permite entre los horarios de inicio y fin configurados. El horario puede ajustarse según los cambios de horario de verano para Londres o Nueva York.
  2. Rango de apertura – Justo antes de que comience la sesión, la estrategia registra los máximos y mínimos de un número fijo de velas. Este rango proporciona los niveles de rompimiento usados durante la ventana de trading.
  3. Confirmación de tendencia – La pendiente de la media móvil exponencial (EMA) debe estar de acuerdo con la dirección de la operación. Un rompimiento alcista requiere que la EMA suba, mientras que un rompimiento bajista requiere que caiga.
  4. Filtro de momentum – El oscilador estocástico debe permanecer dentro de una zona configurable alrededor del punto medio para evitar entrar cuando el mercado ya está sobrecomprado o sobrevendido.
  5. Validación del rompimiento – La vela anterior debe cerrar más allá del máximo o mínimo del rango al menos la distancia mínima de rompimiento pero no más lejos que la distancia máxima.
  6. Controles de riesgo – Cada posición define un stop loss desde el límite del rango y un objetivo de take-profit basado en un factor de esa distancia de stop. Un trailing stop puede ajustar la salida a medida que avanza la operación, y las posiciones pueden cerrarse forzosamente después de cierto número de velas.

Parámetros

Nombre Descripción
StartTrade Hora de inicio de sesión.
EndTrade Hora de fin de sesión.
SwitchDst Manejo de horario de verano: Europa (sin cambio), EE.UU. (cambio relativo entre Londres y Nueva York), o desactivado.
RiskModes Modo de dimensionamiento de posición (porcentaje del capital o volumen fijo).
PositionRisk Porcentaje de riesgo o volumen fijo, dependiendo del modo.
TradeRange Número de velas usadas para construir el rango de apertura.
MinRangePoints / MaxRangePoints Tamaño mínimo y máximo del rango de apertura, en puntos de precio.
MinBreakRange / MaxBreakRange Distancia de rompimiento mínima y máxima aceptable por encima o por debajo del rango, en puntos de precio.
StopLossPoints Distancia del stop-loss medida desde el lado opuesto del rango, en puntos de precio.
TpFactor Multiplicador de take-profit aplicado a la distancia del stop-loss.
TrailStopPoints Distancia opcional del trailing stop, en puntos de precio. Poner en cero para deshabilitar el trailing.
TrendPeriod Período para el filtro de pendiente EMA.
OverPeriod Período para el oscilador estocástico.
OverLevels Distancia desde 50 usada para definir el rango aceptable del estocástico.
BarsToClose Número máximo de velas para mantener la posición abierta. Cero deshabilita el tiempo de espera.
MaxSpreadPoints Máximo spread permitido en puntos de precio.
SlippagePoints Deslizamiento de referencia en puntos de precio (mantenido por compatibilidad con el asesor experto original).
CandleType Tipo de vela y marco temporal procesados por la estrategia.

Notas de uso

  • La estrategia está diseñada para instrumentos cotizados con un paso de precio fijo. Las entradas basadas en puntos se multiplican por el PriceStep del instrumento para obtener distancias de precio.
  • Los datos del libro de órdenes se usan para estimar el spread actual. Si las mejores cotizaciones bid/ask no están disponibles, el filtrado de spread se omite.
  • Las salidas de trailing y tiempo de espera se evalúan en velas cerradas, coincidiendo con la lógica MQL original.
  • El dimensionamiento de posición requiere la valoración de la cartera (Portfolio.CurrentValue) cuando RiskModes está configurado en porcentaje. Si el valor no está disponible, la estrategia vuelve al tamaño de lote configurado.

Archivos

  • CS/V1n1LonnyBreakoutStrategy.cs – Implementación de la estrategia en C# para StockSharp.
  • README.md – Esta descripción en inglés.
  • README_zh.md – 中文简介。
  • README_ru.md – Descripción en ruso.
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>
/// Breakout strategy that mirrors the original "V1N1 LONNY" MQL expert advisor.
/// The strategy forms an opening range from early candles and
/// enters when a candle closes outside that range while trend and momentum filters agree.
/// </summary>
public class V1n1LonnyBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _trendPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _rangeBars;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _ema;
	private RelativeStrengthIndex _rsi;

	private decimal _prevEma;
	private decimal _prevPrevEma;
	private bool _hasPrevEma;
	private bool _hasPrevPrevEma;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private bool _rangeReady;
	private decimal _rangeHigh;
	private decimal _rangeLow;
	private bool _breakoutUpSeen;
	private bool _breakoutDownSeen;

	/// <summary>
	/// EMA period for the trend filter.
	/// </summary>
	public int TrendPeriod
	{
		get => _trendPeriod.Value;
		set => _trendPeriod.Value = value;
	}

	/// <summary>
	/// RSI period for momentum filter.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Number of initial bars to build the opening range.
	/// </summary>
	public int RangeBars
	{
		get => _rangeBars.Value;
		set => _rangeBars.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="V1n1LonnyBreakoutStrategy"/> class.
	/// </summary>
	public V1n1LonnyBreakoutStrategy()
	{
		_trendPeriod = Param(nameof(TrendPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Trend EMA", "EMA period for trend filter", "Indicators");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period for momentum filter", "Indicators");

		_rangeBars = Param(nameof(RangeBars), 5)
			.SetGreaterThanZero()
			.SetDisplay("Range Bars", "Bars used to build the opening range", "Breakout");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle type and timeframe", "General");
	}

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

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

		_ema = null;
		_rsi = null;
		_prevEma = 0m;
		_prevPrevEma = 0m;
		_hasPrevEma = false;
		_hasPrevPrevEma = false;

		_highs.Clear();
		_lows.Clear();
		_rangeReady = false;
		_rangeHigh = 0m;
		_rangeLow = 0m;
		_breakoutUpSeen = false;
		_breakoutDownSeen = false;
	}

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

		_ema = new ExponentialMovingAverage { Length = TrendPeriod };
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		Indicators.Add(_ema);
		Indicators.Add(_rsi);

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

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent));

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

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

		var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });

		if (!_rsi.IsFormed || !_ema.IsFormed)
		{
			ShiftEma(emaValue);
			return;
		}

		var rsiValue = rsiResult.ToDecimal();

		// Build the opening range from the first N bars
		if (!_rangeReady)
		{
			_highs.Add(candle.HighPrice);
			_lows.Add(candle.LowPrice);

			if (_highs.Count >= RangeBars)
			{
				_rangeHigh = decimal.MinValue;
				_rangeLow = decimal.MaxValue;

				for (var i = 0; i < _highs.Count; i++)
				{
					if (_highs[i] > _rangeHigh) _rangeHigh = _highs[i];
					if (_lows[i] < _rangeLow) _rangeLow = _lows[i];
				}

				_rangeReady = true;
			}

			ShiftEma(emaValue);
			return;
		}

		if (Position != 0 || !_hasPrevEma || !_hasPrevPrevEma)
		{
			ShiftEma(emaValue);
			return;
		}

		// Trend rising: EMA going up
		var trendUp = _prevEma > _prevPrevEma;
		var trendDown = _prevEma < _prevPrevEma;

		// Long: close above range high + trend up + RSI not overbought
		if (!_breakoutUpSeen && trendUp && rsiValue < 70 && candle.ClosePrice > _rangeHigh)
		{
			BuyMarket();
			_breakoutUpSeen = true;
		}
		// Short: close below range low + trend down + RSI not oversold
		else if (!_breakoutDownSeen && trendDown && rsiValue > 30 && candle.ClosePrice < _rangeLow)
		{
			SellMarket();
			_breakoutDownSeen = true;
		}

		ShiftEma(emaValue);
	}

	private void ShiftEma(decimal emaValue)
	{
		if (_hasPrevEma)
		{
			_prevPrevEma = _prevEma;
			_hasPrevPrevEma = true;
		}

		_prevEma = emaValue;
		_hasPrevEma = true;
	}
}