GitHub で見る

V1N1 Lonny Breakout戦略

概要

V1N1 Lonny Breakout戦略はMetaTraderのエキスパートアドバイザー「V1N1 LONNY」を再現しています。ロンドンとニューヨークのセッション周辺で生じるブレイクアウトを狙い、オープニングレンジを構築してそのレンジの外への決定的な終値を待ちます。戦略は指数移動平均を使って優勢なトレンドを捉え、市場に参入する前に買われすぎや売られすぎの状態をフィルタリングするためにストキャスティクスオシレーターを使用します。

設定可能なリスクモデルにより、固定ボリュームまたはアカウント資本のパーセンテージでポジションサイズを設定できます。実装にはオプションのスプレッドフィルタリング、トレーリングストップ、および所定のローソク足数後にモメンタムが薄れた場合にトレードを閉じるバーベースのタイムアウトも含まれています。

トレードロジック

  1. セッション整合 – 取引は設定された開始時刻と終了時刻の間のみ許可されます。スケジュールはロンドンまたはニューヨークのサマータイムに従って調整できます。
  2. オープニングレンジ – セッション開始直前に、戦略は固定数のローソク足の高値と安値を記録します。このレンジは取引ウィンドウ中に使用されるブレイクアウトレベルを提供します。
  3. トレンド確認 – 指数移動平均(EMA)の傾きがトレード方向と一致している必要があります。強気のブレイクアウトにはEMAが上昇している必要があり、弱気のブレイクアウトにはEMAが下落している必要があります。
  4. モメンタムフィルター – 市場がすでに買われすぎや売られすぎの時のエントリーを避けるために、ストキャスティクスオシレーターは中間点周辺の設定可能なゾーン内に留まる必要があります。
  5. ブレイクアウト検証 – 前のローソク足はレンジの高値または安値を最小ブレイクアウト距離以上だが最大距離を超えない範囲で越えて閉じる必要があります。
  6. リスクコントロール – 各ポジションはレンジ境界からのストップロスと、そのストップ距離の係数に基づくテイクプロフィット目標を定義します。トレーリングストップはトレードが進むにつれて決済を絞り込むことができ、一定数のローソク足後にポジションを強制的に閉じることができます。

パラメーター

名前 説明
StartTrade セッション開始時刻。
EndTrade セッション終了時刻。
SwitchDst サマータイム処理:ヨーロッパ(シフトなし)、USA(ロンドンとニューヨーク間の相対シフト)、または無効。
RiskModes ポジションサイジングモード(資本のパーセンテージまたは固定ボリューム)。
PositionRisk モードに応じたリスクパーセンテージまたは固定ボリューム。
TradeRange オープニングレンジを構築するために使用するローソク足の数。
MinRangePoints / MaxRangePoints オープニングレンジの最小および最大サイズ(価格ポイント)。
MinBreakRange / MaxBreakRange レンジの上下に対して許容される最小および最大ブレイクアウト距離(価格ポイント)。
StopLossPoints レンジの反対側から測定したストップロス距離(価格ポイント)。
TpFactor ストップロス距離に適用されるテイクプロフィット乗数。
TrailStopPoints オプションのトレーリングストップ距離(価格ポイント)。トレーリングを無効にするにはゼロに設定。
TrendPeriod EMA傾きフィルターの期間。
OverPeriod ストキャスティクスオシレーターの期間。
OverLevels 許容可能なストキャスティクス範囲を定義するために使用する50からの距離。
BarsToClose ポジションをオープンに保つための最大ローソク足数。ゼロはタイムアウトを無効にします。
MaxSpreadPoints 価格ポイントで許可される最大スプレッド。
SlippagePoints 価格ポイントでの参考スリッページ(元のエキスパートアドバイザーとの互換性のために保持)。
CandleType 戦略が処理するローソク足タイプと時間軸。

使用上の注意

  • 戦略は固定価格ステップで見積もられるインストゥルメント向けに設計されています。ポイントベースの入力は価格距離を得るためにインストゥルメントのPriceStepで乗算されます。
  • 現在のスプレッドを推定するためにオーダーブックデータが使用されます。最良のbid/ask気配値が利用できない場合、スプレッドフィルタリングはスキップされます。
  • トレーリングとタイムアウトの決済は、元のMQLロジックに合わせて閉じたローソク足で評価されます。
  • ポジションサイジングはRiskModesがパーセンテージに設定されている場合、ポートフォリオ評価(Portfolio.CurrentValue)が必要です。値が利用できない場合、戦略は設定されたロットサイズにフォールバックします。

ファイル

  • CS/V1n1LonnyBreakoutStrategy.cs – StockSharp向けC#での戦略実装。
  • README.md – この英語の説明。
  • README_zh.md – 中文简介。
  • README_ru.md – ロシア語の説明。
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;
	}
}