GitHub で見る

EurUsd セッション・ブレイクアウト戦略

この戦略は、欧州の午前中の狭いレンジを米国セッションへのバネとして使用するという古典的な EUR/USD ブレイクアウトのアイデアを複製します。デフォルトで 15 分ローソク足の 24 本のローリングウィンドウを監視して米国前のトレーディングレンジを測定し、レンジが設定可能な pip 閾値を超えた日をフィルタリングし、そのバンドの完全に外側で発生するブレイクアウトをトレードします。トレーディング日ごとにロングとショートの試行がそれぞれ 1 回のみ許可されます。

仕組み

  1. セッション追跡 – 設定された米国セッション時間の開始時に、戦略は直近 24 本の完成したローソク足(現在のバーを除く)によって捉えられた EU レンジをロックします。レンジは 3 桁または 5 桁の外国為替クォートに対して自動的に pip 値に調整されます。
  2. レンジフィルター – キャプチャされた EU レンジがEU セッション小 (pips) 閾値より小さい場合にのみトレーディングが有効になります。
  3. ブレイクアウト検証 – 許可された米国セッション時間中、および (EU 開始時間 + 5) から (EU 開始時間 + 10) の間のみ、戦略はポイントで測定された追加バッファーを持つ保存されたレンジの外側で完全にトレードされたローソク足を探します。
  4. 注文執行 – バーの安値がレンジの上限プラスバッファーより上に留まる場合、成行買い注文が送られます。バーの高値がレンジの下限マイナスバッファーより下に留まる場合、成行売り注文が送られます。ロングとショートのトレードは独立したフラグであるため、各方向は 1 日 1 回試行できます。
  5. リスク管理 – ストップロスとテイクプロフィットレベルは pip で定義され、絶対価格距離に変換され、高値/安値の極値を使用して各完成したローソク足で追跡されます。

パラメーター

  • EU セッション開始 / US セッション開始 / US セッション終了 – EU モニタリングが開始される時刻と US ブレイクアウトウィンドウが開いている時刻を定義する時間(0–23)。
  • EU セッション小 (pips) – トレーディングをまだ許可する EU レンジの最大サイズ。
  • 月曜日に取引 – 週末をブロックしながら月曜日の取引を有効または無効にします。
  • ストップロス (pips) – エントリー価格と保護ストップ間の距離、ティックサイズと桁数によって自動的にスケーリング。
  • テイクプロフィット (pips) – 利益目標距離、ストップと同じ方法で処理。
  • ブレイクアウトバッファー (ポイント) – 確認バーが保存されたレンジを完全に超えなければならないように、ブレイクアウトトリガーに追加される価格ステップ数。
  • ローソク足タイプ – ローソク足サブスクリプションのデータタイプ;元のスクリプトが M15 チャート用に設計されていたため、デフォルトは 15 分時間軸。

追加事項

  • 戦略はネッティング口座を前提とします:保護レベルは成行注文を使用してポジション全体をフラットにします。
  • 日次状態は深夜にリセットされるため、レンジとブレイクアウトフラグはセッションをまたがって漏れません。一方、オープンポジションは価格ターゲットを保持します。
  • ストップロスとテイクプロフィットレベルはローソク足の極値でシミュレートされるため、履歴バーに表示されないイントラバースパイクは検出されません。
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 trades after a tight consolidation range.
/// Captures the range from a "quiet" session, then trades breakouts in the following session.
/// </summary>
public class EurUsdSessionBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _euSessionLengthBars;
	private readonly StrategyParam<int> _startHourRangeSession;
	private readonly StrategyParam<int> _startHourTradeSession;
	private readonly StrategyParam<int> _endHourTradeSession;
	private readonly StrategyParam<decimal> _smallSessionThreshold;
	private readonly StrategyParam<decimal> _stopLossDistance;
	private readonly StrategyParam<decimal> _takeProfitDistance;
	private readonly StrategyParam<decimal> _breakoutBuffer;
	private readonly StrategyParam<DataType> _candleType;

	private Highest _highest = null!;
	private Lowest _lowest = null!;
	private decimal _currentHighest;
	private decimal _currentLowest;
	private decimal _entryPrice;
	private decimal _stopPrice;
	private decimal _takePrice;
	private DateTime _currentDate;

	public EurUsdSessionBreakoutStrategy()
	{
		_startHourRangeSession = Param(nameof(StartHourRangeSession), 0)
			.SetDisplay("Range Session Start", "Start hour of the consolidation range session", "Schedule");

		_startHourTradeSession = Param(nameof(StartHourTradeSession), 8)
			.SetDisplay("Trade Session Start", "Start hour of the trading session", "Schedule");

		_endHourTradeSession = Param(nameof(EndHourTradeSession), 20)
			.SetDisplay("Trade Session End", "End hour of the trading session", "Schedule");

		_smallSessionThreshold = Param(nameof(SmallSessionThreshold), 200m)
			.SetDisplay("Small Session Threshold", "Maximum range session price range to trigger trading", "Risk");

		_stopLossDistance = Param(nameof(StopLossDistance), 5m)
			.SetDisplay("Stop Loss Distance", "Stop loss distance in price units", "Risk");

		_takeProfitDistance = Param(nameof(TakeProfitDistance), 8m)
			.SetDisplay("Take Profit Distance", "Take profit distance in price units", "Risk");

		_breakoutBuffer = Param(nameof(BreakoutBuffer), 0m)
			.SetDisplay("Breakout Buffer", "Extra price buffer added to breakout trigger", "Entries");

		_euSessionLengthBars = Param(nameof(EuSessionLengthBars), 10)
			.SetRange(1, 72)
			.SetDisplay("Range Session Length (bars)", "Number of bars representing the range session", "Schedule");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle type used for calculations", "General");
	}

	public int StartHourRangeSession
	{
		get => _startHourRangeSession.Value;
		set => _startHourRangeSession.Value = value;
	}

	public int StartHourTradeSession
	{
		get => _startHourTradeSession.Value;
		set => _startHourTradeSession.Value = value;
	}

	public int EndHourTradeSession
	{
		get => _endHourTradeSession.Value;
		set => _endHourTradeSession.Value = value;
	}

	public decimal SmallSessionThreshold
	{
		get => _smallSessionThreshold.Value;
		set => _smallSessionThreshold.Value = value;
	}

	public decimal StopLossDistance
	{
		get => _stopLossDistance.Value;
		set => _stopLossDistance.Value = value;
	}

	public decimal TakeProfitDistance
	{
		get => _takeProfitDistance.Value;
		set => _takeProfitDistance.Value = value;
	}

	public decimal BreakoutBuffer
	{
		get => _breakoutBuffer.Value;
		set => _breakoutBuffer.Value = value;
	}

	public int EuSessionLengthBars
	{
		get => _euSessionLengthBars.Value;
		set => _euSessionLengthBars.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highest = null!;
		_lowest = null!;
		_currentHighest = 0;
		_currentLowest = 0;
		_entryPrice = 0;
		_stopPrice = 0;
		_takePrice = 0;
		_currentDate = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_highest = new Highest { Length = EuSessionLengthBars };
		_lowest = new Lowest { Length = EuSessionLengthBars };

		_currentDate = time.Date;

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Manage protective exits first
		ManageActivePosition(candle);

		// Update rolling highest/lowest
		var previousHighest = _currentHighest;
		var previousLowest = _currentLowest;

		_currentHighest = _highest.Process(candle).ToDecimal();
		_currentLowest = _lowest.Process(candle).ToDecimal();

		if (!_highest.IsFormed || !_lowest.IsFormed)
			return;

		if (previousHighest <= 0 || previousLowest <= 0)
			return;

		if (Position != 0)
			return;

		// Breakout above previous rolling highest
		if (candle.ClosePrice > previousHighest + BreakoutBuffer)
		{
			BuyMarket();
			SetLongTargets(candle.ClosePrice);
		}
		// Breakout below previous rolling lowest
		else if (candle.ClosePrice < previousLowest - BreakoutBuffer)
		{
			SellMarket();
			SetShortTargets(candle.ClosePrice);
		}
	}

	private void ManageActivePosition(ICandleMessage candle)
	{
		if (Position == 0)
			return;

		if (Position > 0)
		{
			var exitByStop = StopLossDistance > 0m && candle.LowPrice <= _stopPrice;
			var exitByTake = TakeProfitDistance > 0m && candle.HighPrice >= _takePrice;

			if (exitByStop || exitByTake)
			{
				SellMarket();
				ClearTargets();
			}
		}
		else if (Position < 0)
		{
			var exitByStop = StopLossDistance > 0m && candle.HighPrice >= _stopPrice;
			var exitByTake = TakeProfitDistance > 0m && candle.LowPrice <= _takePrice;

			if (exitByStop || exitByTake)
			{
				BuyMarket();
				ClearTargets();
			}
		}
	}

	private void SetLongTargets(decimal entryPrice)
	{
		_entryPrice = entryPrice;
		_stopPrice = entryPrice - StopLossDistance;
		_takePrice = entryPrice + TakeProfitDistance;
	}

	private void SetShortTargets(decimal entryPrice)
	{
		_entryPrice = entryPrice;
		_stopPrice = entryPrice + StopLossDistance;
		_takePrice = entryPrice - TakeProfitDistance;
	}

	private void ClearTargets()
	{
		_entryPrice = 0m;
		_stopPrice = 0m;
		_takePrice = 0m;
	}

	private void ResetDailyState(DateTime date)
	{
		_currentDate = date;

		if (Position == 0)
			ClearTargets();
	}
}