GitHub で見る

21時間セッションブレイクアウト戦略

この戦略はStockSharp内でMetaTraderの「21hour」エキスパートアドバイザーを再現します。2つの設定可能な取引ウィンドウ中に動作し、保留ストップ注文を使用してレンジの上下でのブレイクアウトを捉えます。各ウィンドウの終わりに、戦略はオープンなエクスポージャーを清算し、アクティブな注文を削除して、毎取引日がクリーンな状態で始まることを保証します。

コアアイデア

  • 取引方向は指定されたセッション開始時刻周辺の価格行動によってのみ決定されます。
  • 各セッションの始まりに、戦略は現在のアスクを上回るバイストップと現在のビッドを下回るセルストップでマーケットを挟みます。
  • ストップ注文が執行されると、反対側は即座にキャンセルされ、固定距離のテイクプロフィット注文が配置されます。
  • 設定されたセッション終了時刻に、すべてのポジションがクローズされ、テイクプロフィットに達していなくても、すべての注文がキャンセルされます。

データフロー

  • ローソク足: 1分ローソク足(設定可能)はタイムスタンプの提供と毎時スケジュールチェックのトリガーにのみ使用されます。
  • 注文帳: レベル1クオートはストップ注文の有効化価格を定義する現在の最良ビッド/アスク値を提供します。

取引ルール

エントリースケジューリング

  • FirstSessionStartHour(デフォルト サーバー時刻08:00)およびSecondSessionStartHour(デフォルト22:00)に、戦略は:
    • Ask + StepPoints * PriceStepにバイストップを配置します。
    • Bid - StepPoints * PriceStepにセルストップを配置します。
  • 1つのポジションのみが許可されます。他のセッションが開始したときにすでにポジションが開いている場合、新しいものを配置する前にすべての保留エントリー注文が削除されます。

注文管理

  • ストップ注文の1つが執行されると、反対のストップは即座にキャンセルされます。
  • テイクプロフィット指値注文は取引方向に応じてEntryPrice ± TakeProfitPoints * PriceStepに登録されます。
  • 注文サイズはVolumeパラメーター(デフォルト1ロット)で固定されます。

決済ロジック

  • テイクプロフィット注文は勝ち取引を自動的にクローズします。
  • FirstSessionStopHour(デフォルト21:00)およびSecondSessionStopHour(デフォルト23:00)に、戦略は成行でオープンポジションをクローズし、残りのすべての保留注文をキャンセルします。
  • ポジションが手動でクローズされた場合、戦略は保留中のテイクプロフィット注文も削除します。

パラメーター

パラメーター デフォルト値 説明
Volume 1 ストップエントリーとテイクプロフィット決済に使用する注文ボリューム。
FirstSessionStartHour 8 最初の取引セッションが始まる時刻(0-23)。
FirstSessionStopHour 21 最初のセッションが終わりポジションがクローズされる時刻。
SecondSessionStartHour 22 夜間セッションが始まる時刻。最初のセッション後でなければなりません。
SecondSessionStopHour 23 2番目のセッションが終わる時刻。最初のセッション停止後でなければなりません。
StepPoints 5 最良クオートからエントリーストップ注文までの距離(価格ステップ単位)。
TakeProfitPoints 40 エントリー価格とテイクプロフィットリミットの距離(価格ステップ単位)。
CandleType 1分 イントラデイスケジュールチェックを動かすローソク足タイプ。

すべてのパラメーターは重複するセッションや不可能な時間の組み合わせを避けるために検証されます。

タグと特性

  • スタイル: セッションブレイクアウト / 時間ベースのトレンドフォロー。
  • 方向: ロングとショート。
  • 時間軸: イントラデイ、スケジュール駆動(タイミングのみに1分ローソク足)。
  • リスク管理: 固定テイクプロフィット + セッション終了時の強制フラット(ストップロスなし)。
  • 市場タイプ: 継続的な取引時間と信頼性の高いクオートを持つFX、インデックス、または任意の銘柄向けに設計されています。
  • 複雑さ: 低 – インジケーターなし、純粋に時間と価格ベース。

実装上の注意事項

  • 戦略は有効なSecurity.PriceStepを必要とします。価格ステップまたはクオートが利用できない場合、注文はスキップされます。
  • テイクプロフィットボリュームは利用可能な場合に執行された取引ボリュームを使用し、現在のポジションまたは設定されたボリュームにフォールバックします。
  • コードはStockSharpの高レベルAPI(SubscribeCandlesSubscribeOrderBook、ヘルパーパラメーター、注文ヘルパー)を活用しながら、オリジナルのMQLロジックを反映した英語のインラインコメントを維持しています。
using System;
using System.Linq;
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>
/// 21-hour session breakout strategy. Places simulated stop entries via candle breakout logic.
/// </summary>
public class TwentyOneHourSessionBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _firstSessionStartHour;
	private readonly StrategyParam<int> _firstSessionStopHour;
	private readonly StrategyParam<decimal> _stepPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal? _sessionOpen;
	private decimal _entryPrice;
	private bool _inSession;

	public int FirstSessionStartHour
	{
		get => _firstSessionStartHour.Value;
		set => _firstSessionStartHour.Value = value;
	}

	public int FirstSessionStopHour
	{
		get => _firstSessionStopHour.Value;
		set => _firstSessionStopHour.Value = value;
	}

	public decimal StepPoints
	{
		get => _stepPoints.Value;
		set => _stepPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

	public TwentyOneHourSessionBreakoutStrategy()
	{
		_firstSessionStartHour = Param(nameof(FirstSessionStartHour), 2)
			.SetDisplay("Session Start", "Hour of the trading window start", "Schedule");

		_firstSessionStopHour = Param(nameof(FirstSessionStopHour), 20)
			.SetDisplay("Session Stop", "Hour of the trading window stop", "Schedule");

		_stepPoints = Param(nameof(StepPoints), 40m)
			.SetGreaterThanZero()
			.SetDisplay("Step Points", "Distance from session open to breakout level", "Orders");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit Points", "Take-profit distance", "Orders");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles used to drive the trading schedule", "Data");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_sessionOpen = null;
		_entryPrice = 0m;
		_inSession = false;
	}

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

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

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

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

		var hour = candle.OpenTime.Hour;
		var priceStep = Security?.PriceStep ?? 1m;

		// Session start: record the open price
		if (hour >= FirstSessionStartHour && hour < FirstSessionStopHour)
		{
			if (!_inSession)
			{
				_sessionOpen = candle.OpenPrice;
				_inSession = true;
			}

			if (_sessionOpen == null)
				return;

			var stepOffset = StepPoints * priceStep;
			var buyLevel = _sessionOpen.Value + stepOffset;
			var sellLevel = _sessionOpen.Value - stepOffset;

			// Breakout entry
			if (Position == 0)
			{
				if (candle.HighPrice >= buyLevel)
				{
					BuyMarket();
					_entryPrice = buyLevel;
				}
				else if (candle.LowPrice <= sellLevel)
				{
					SellMarket();
					_entryPrice = sellLevel;
				}
			}

			// Take profit
			if (Position > 0)
			{
				var tp = _entryPrice + TakeProfitPoints * priceStep;
				if (candle.HighPrice >= tp)
				{
					SellMarket();
					_sessionOpen = candle.ClosePrice;
				}
			}
			else if (Position < 0)
			{
				var tp = _entryPrice - TakeProfitPoints * priceStep;
				if (candle.LowPrice <= tp)
				{
					BuyMarket();
					_sessionOpen = candle.ClosePrice;
				}
			}
		}
		else
		{
			// Session end: close position
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			_inSession = false;
			_sessionOpen = null;
		}
	}
}