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
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.
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.
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.
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.
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.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class v1n1_lonny_breakout_strategy(Strategy):
def __init__(self):
super(v1n1_lonny_breakout_strategy, self).__init__()
self._trend_period = self.Param("TrendPeriod", 20)
self._rsi_period = self.Param("RsiPeriod", 14)
self._range_bars = self.Param("RangeBars", 5)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._has_prev_ema = False
self._has_prev_prev_ema = False
self._highs = []
self._lows = []
self._range_ready = False
self._range_high = 0.0
self._range_low = 0.0
self._breakout_up_seen = False
self._breakout_down_seen = False
@property
def TrendPeriod(self):
return self._trend_period.Value
@TrendPeriod.setter
def TrendPeriod(self, value):
self._trend_period.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RangeBars(self):
return self._range_bars.Value
@RangeBars.setter
def RangeBars(self, value):
self._range_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(v1n1_lonny_breakout_strategy, self).OnStarted2(time)
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._has_prev_ema = False
self._has_prev_prev_ema = False
self._highs = []
self._lows = []
self._range_ready = False
self._range_high = 0.0
self._range_low = 0.0
self._breakout_up_seen = False
self._breakout_down_seen = False
ema = ExponentialMovingAverage()
ema.Length = self.TrendPeriod
self._ema = ema
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent))
def ProcessCandle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
ema_val = float(ema_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
rsi_result = process_float(self._rsi, candle.ClosePrice, candle.OpenTime, True)
if not self._rsi.IsFormed or not self._ema.IsFormed:
self._shift_ema(ema_val)
return
rsi_val = float(rsi_result)
if not self._range_ready:
self._highs.append(high)
self._lows.append(low)
if len(self._highs) >= int(self.RangeBars):
self._range_high = max(self._highs)
self._range_low = min(self._lows)
self._range_ready = True
self._shift_ema(ema_val)
return
if self.Position != 0 or not self._has_prev_ema or not self._has_prev_prev_ema:
self._shift_ema(ema_val)
return
trend_up = self._prev_ema > self._prev_prev_ema
trend_down = self._prev_ema < self._prev_prev_ema
if not self._breakout_up_seen and trend_up and rsi_val < 70.0 and close > self._range_high:
self.BuyMarket()
self._breakout_up_seen = True
elif not self._breakout_down_seen and trend_down and rsi_val > 30.0 and close < self._range_low:
self.SellMarket()
self._breakout_down_seen = True
self._shift_ema(ema_val)
def _shift_ema(self, ema_val):
if self._has_prev_ema:
self._prev_prev_ema = self._prev_ema
self._has_prev_prev_ema = True
self._prev_ema = ema_val
self._has_prev_ema = True
def OnReseted(self):
super(v1n1_lonny_breakout_strategy, self).OnReseted()
self._prev_ema = 0.0
self._prev_prev_ema = 0.0
self._has_prev_ema = False
self._has_prev_prev_ema = False
self._highs = []
self._lows = []
self._range_ready = False
self._range_high = 0.0
self._range_low = 0.0
self._breakout_up_seen = False
self._breakout_down_seen = False
def CreateClone(self):
return v1n1_lonny_breakout_strategy()