Ver no GitHub

Estratégia V1N1 Lonny Breakout

Visão geral

A estratégia V1N1 Lonny Breakout replica o consultor especialista do MetaTrader "V1N1 LONNY". Tem como alvo rompimentos que emergem em torno das sessões de Londres e Nova York, construindo um intervalo de abertura e esperando por um fechamento decisivo fora desse intervalo. A estratégia se baseia em uma média móvel exponencial para capturar a tendência predominante e em um oscilador estocástico para filtrar condições de sobrecompra ou sobrevenda antes de entrar no mercado.

Um modelo de risco configurável permite dimensionar posições por volume fixo ou como porcentagem do capital da conta. A implementação também inclui filtragem de spread opcional, trailing stops e um timeout baseado em barras que fecha a operação se o momentum desaparecer após um número predefinido de velas.

Lógica de trading

  1. Alinhamento de sessão – O trading só é permitido entre os horários de início e fim configurados. O cronograma pode ser ajustado de acordo com os horários de verão para Londres ou Nova York.
  2. Intervalo de abertura – Imediatamente antes do início da sessão, a estratégia registra as máximas e mínimas de um número fixo de velas. Este intervalo fornece os níveis de rompimento usados durante a janela de trading.
  3. Confirmação de tendência – A inclinação da média móvel exponencial (EMA) deve concordar com a direção da operação. Um rompimento de alta requer que a EMA suba, enquanto um rompimento de baixa requer que caia.
  4. Filtro de momentum – O oscilador estocástico deve permanecer dentro de uma zona configurável em torno do ponto médio para evitar entrar quando o mercado já está sobrecomprado ou sobrevendido.
  5. Validação do rompimento – A vela anterior deve fechar além da máxima ou mínima do intervalo pelo menos a distância mínima de rompimento, mas não mais longe que a distância máxima.
  6. Controles de risco – Cada posição define um stop loss a partir do limite do intervalo e um alvo de take-profit baseado em um fator dessa distância de stop. Um trailing stop pode apertar a saída conforme a operação avança, e as posições podem ser fechadas forçosamente após um certo número de velas.

Parâmetros

Nome Descrição
StartTrade Hora de início da sessão.
EndTrade Hora de fim da sessão.
SwitchDst Tratamento de horário de verão: Europa (sem mudança), EUA (mudança relativa entre Londres e Nova York), ou desativado.
RiskModes Modo de dimensionamento de posição (porcentagem do capital ou volume fixo).
PositionRisk Porcentagem de risco ou volume fixo, dependendo do modo.
TradeRange Número de velas usadas para construir o intervalo de abertura.
MinRangePoints / MaxRangePoints Tamanho mínimo e máximo do intervalo de abertura, em pontos de preço.
MinBreakRange / MaxBreakRange Distância de rompimento mínima e máxima aceitável acima ou abaixo do intervalo, em pontos de preço.
StopLossPoints Distância do stop-loss medida a partir do lado oposto do intervalo, em pontos de preço.
TpFactor Multiplicador de take-profit aplicado à distância do stop-loss.
TrailStopPoints Distância opcional do trailing stop, em pontos de preço. Definir como zero para desabilitar o trailing.
TrendPeriod Período para o filtro de inclinação EMA.
OverPeriod Período para o oscilador estocástico.
OverLevels Distância a partir de 50 usada para definir o intervalo aceitável do estocástico.
BarsToClose Número máximo de velas para manter a posição aberta. Zero desabilita o timeout.
MaxSpreadPoints Spread máximo permitido em pontos de preço.
SlippagePoints Slippage de referência em pontos de preço (mantido por compatibilidade com o consultor especialista original).
CandleType Tipo de vela e período processados pela estratégia.

Notas de uso

  • A estratégia é projetada para instrumentos cotados com um passo de preço fixo. As entradas baseadas em pontos são multiplicadas pelo PriceStep do instrumento para obter distâncias de preço.
  • Dados do livro de ordens são usados para estimar o spread atual. Se as melhores cotações bid/ask não estiverem disponíveis, a filtragem de spread é ignorada.
  • As saídas de trailing e timeout são avaliadas em velas fechadas, correspondendo à lógica MQL original.
  • O dimensionamento de posição requer a valoração do portfólio (Portfolio.CurrentValue) quando RiskModes está configurado para porcentagem. Se o valor não estiver disponível, a estratégia volta ao tamanho de lote configurado.

Arquivos

  • CS/V1n1LonnyBreakoutStrategy.cs – Implementação da estratégia em C# para StockSharp.
  • README.md – Esta descrição em inglês.
  • README_zh.md – 中文简介。
  • README_ru.md – Descrição em russo.
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;
	}
}