Die V1N1 Lonny Breakout-Strategie repliziert den MetaTrader Expert Advisor "V1N1 LONNY". Sie zielt auf Ausbrüche ab, die rund um die London- und New-York-Sitzungen entstehen, indem eine Eröffnungsrange aufgebaut und auf einen entschiedenen Schlusskurs außerhalb dieser Range gewartet wird. Die Strategie stützt sich auf einen exponentiellen gleitenden Durchschnitt, um den vorherrschenden Trend zu erfassen, und auf einen Stochastik-Oszillator, um überkaufte oder überverkaufte Bedingungen vor dem Markteintritt herauszufiltern.
Ein konfigurierbares Risikomodell ermöglicht die Positionsgrößenbestimmung durch festes Volumen oder als Prozentsatz des Kontokapitals. Die Implementierung beinhaltet auch optionale Spread-Filterung, Trailing-Stops und ein balkenbasiertes Timeout, das den Trade schließt, wenn der Schwung nach einer vordefinierten Anzahl von Kerzen nachlässt.
Handelslogik
Sitzungsausrichtung – Handel ist nur zwischen den konfigurierten Start- und Endzeiten erlaubt. Der Zeitplan kann gemäß Sommerzeit-Regelungen für London oder New York verschoben werden.
Eröffnungsrange – Unmittelbar vor Sitzungsbeginn zeichnet die Strategie die Hochs und Tiefs einer festen Anzahl von Kerzen auf. Diese Range liefert die Ausbruchsniveaus, die während des Handelsfensters verwendet werden.
Trendbestätigung – Die Steigung des exponentiellen gleitenden Durchschnitts (EMA) muss mit der Handelsrichtung übereinstimmen. Ein bullischer Ausbruch erfordert einen steigenden EMA, während ein bärischer Ausbruch einen fallenden EMA erfordert.
Momentum-Filter – Der Stochastik-Oszillator muss innerhalb einer konfigurierbaren Zone um den Mittelpunkt bleiben, um zu vermeiden, dass eingestiegen wird, wenn der Markt bereits überkauft oder überverkauft ist.
Ausbruchsvalidierung – Die vorherige Kerze muss mindestens um den minimalen Ausbruchsabstand über dem Range-Hoch oder -Tief schließen, aber nicht weiter als der maximale Abstand.
Risikokontrollen – Jede Position definiert einen Stop-Loss von der Range-Grenze und ein Take-Profit-Ziel basierend auf einem Faktor dieses Stop-Abstands. Ein Trailing-Stop kann den Ausstieg enger setzen, während der Trade fortschreitet, und Positionen können nach einer bestimmten Anzahl von Kerzen zwangsweise geschlossen werden.
Parameter
Name
Beschreibung
StartTrade
Sitzungsstartzeit.
EndTrade
Sitzungsendzeit.
SwitchDst
Sommerzeit-Behandlung: Europa (kein Versatz), USA (relativer Versatz zwischen London und New York) oder deaktiviert.
RiskModes
Positionsgrößenmodus (Prozentsatz des Kapitals oder festes Volumen).
PositionRisk
Risikoprozentsatz oder festes Volumen, je nach Modus.
TradeRange
Anzahl der Kerzen zum Aufbau der Eröffnungsrange.
MinRangePoints / MaxRangePoints
Minimale und maximale Größe der Eröffnungsrange in Preispunkten.
MinBreakRange / MaxBreakRange
Minimaler und maximaler akzeptabler Ausbruchsabstand über oder unter der Range in Preispunkten.
StopLossPoints
Stop-Loss-Abstand gemessen von der gegenüberliegenden Seite der Range in Preispunkten.
TpFactor
Take-Profit-Multiplikator angewendet auf den Stop-Loss-Abstand.
TrailStopPoints
Optionaler Trailing-Stop-Abstand in Preispunkten. Auf null setzen, um das Trailing zu deaktivieren.
TrendPeriod
Periode für den EMA-Steigungsfilter.
OverPeriod
Periode für den Stochastik-Oszillator.
OverLevels
Abstand von 50, der zur Definition des akzeptablen Stochastik-Bereichs verwendet wird.
BarsToClose
Maximale Anzahl von Kerzen, die die Position offen halten. Null deaktiviert das Timeout.
MaxSpreadPoints
Maximaler erlaubter Spread in Preispunkten.
SlippagePoints
Referenz-Slippage in Preispunkten (zur Kompatibilität mit dem ursprünglichen Expert Advisor beibehalten).
CandleType
Von der Strategie verarbeiteter Kerzentyp und Zeitrahmen.
Verwendungshinweise
Die Strategie ist für Instrumente mit einem festen Preisschritt konzipiert. Punktbasierte Eingaben werden mit dem PriceStep des Instruments multipliziert, um Preisabstände zu erhalten.
Orderbuchdaten werden zur Schätzung des aktuellen Spreads verwendet. Wenn keine besten Bid/Ask-Kurse verfügbar sind, wird die Spread-Filterung übersprungen.
Trailing- und Timeout-Ausstiege werden auf geschlossenen Kerzen ausgewertet, entsprechend der ursprünglichen MQL-Logik.
Die Positionsgrößenbestimmung erfordert eine Portfoliobewertung (Portfolio.CurrentValue), wenn RiskModes auf Prozentsatz eingestellt ist. Wenn der Wert nicht verfügbar ist, greift die Strategie auf die konfigurierte Losgröße zurück.
Dateien
CS/V1n1LonnyBreakoutStrategy.cs – Strategie-Implementierung in C# für StockSharp.
README.md – Diese Beschreibung auf Englisch.
README_zh.md – 中文简介。
README_ru.md – Russische Beschreibung.
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()