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
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.
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.
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.
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.
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.
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;
}
}
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()