GitHub で見る

21時間戦略

概要

21hour 戦略は、MQL4 エキスパート アドバイザー 21hour.mq4 の動作を再現します。これは毎日の時間枠で動作します。保留中のブレイクアウト注文は設定可能な開始時間に作成され、すべてのエクスポージャは設定可能な終了時間に削除されます。 StockSharp バージョンでは、同じ「価格付近の 2 つのストップ注文」というアイデアを維持しながら、注文管理、市場データのサブスクリプション、保護的なテイクプロフィット処理に高レベルの API を活用しています。

取引ロジック

  • 各取引日の開始時に、サーバー時間が StartHour:00 と一致すると、ストラテジーは最新の買値/売値を読み取り、買いストップ注文と売りストップ注文の両方を発注します。
    • 現在の売値から買いストップトリガーまでの距離は StepPoints * PriceStep です。
    • 現在の入札価格から売りストップトリガーまでの距離は、市場よりも同じだけ下回ります。
    • TakeProfitPoints は商品価格ステップを通じて価格距離に変換され、StartProtection に渡されるため、ロングポジションとショートポジションの両方が約定直後に保護的なテイクプロフィットを受け取ります。
  • 保留中のセットアップは 1 日に 1 つだけ許可されます。 2 つの逆指値注文のうち 1 つだけがアクティブなままの場合 (たとえば、一方の注文が約定された後)、ストラテジーは残っている未決注文をキャンセルして、元の EA ロジックを反映します。
  • 時計が StopHour:00 に達すると、この戦略は市場でオープンなポジションをすべて閉じ、未処理の未決注文をすべてキャンセルします。これは、ブレイクアウトが発生していない場合でも当てはまります。
  • デフォルトのローソク足ストリームは 1 分間のデータです。これは、完成したキャンドルの 1 時間ごとのチェックをトリガーするためにのみ使用され、MQL バージョンの prevtime ガードを模倣します。

パラメーター

パラメータ 説明 デフォルト
Volume 両方の未決注文のロット単位の注文量。 0.1
StartHour 未決注文のペアが作成される時間 (0 ~ 23)。 10
StopHour ストラテジーがポジションをクローズし、すべての未決注文を削除する時間 (0 ~ 23)。 22
StepPoints 現在の買値/売値と各ストップエントリー間の商品ポイント単位の距離。 PriceStepを掛けて価格に換算します。 15
TakeProfitPoints StartProtection によって管理されるエントリー価格から利益確定ターゲットまでのポイント単位の距離。ターゲットを無効にするには、0 に設定します。 200
CandleType 時間追跡に使用されるキャンドル データ タイプ。デフォルトは 1 分の時間枠 (TimeSpan.FromMinutes(1).TimeFrame()) です。 1 minute

実装メモ

  • SubscribeCandles を使用して完成したキャンドルを取得し、毎分 1 回だけ時間スケジュールを評価します。
  • SubscribeLevel1() 経由でレベル 1 相場を購読し、正確なストップ位置を確保するために最新の買値/売値を維持します。
  • 手動で注文を追加する代わりに、元の EA から未決注文のテイクプロフィットをエミュレートするためのテイクプロフィットユニットを持つ StartProtection に依存します。
  • アクティブな買いと売りのストップ注文を追跡し、一方のみが残っている場合は CancelOrder を呼び出し、ペアになっていない指値注文でシステムが実行されないようにします。
  • 高レベルの戦略 API を厳密に使用して、ストップアワーでポジションをフラット化するために BuyMarket / SellMarket ヘルパーを呼び出します。

行動メモ

  • この戦略では、ブローカー接続が価格ステップ情報を提供することを期待しています。 PriceStep が指定されていない場合、価格は四捨五入されません。
  • 未決注文は暦日に 1 回だけ生成されます。前日のブレイクアウトがトリガーされなかった場合でも、次の取引日の設定された開始時間に再作成されます。
  • TakeProfitPoints がゼロの場合、戦略は未決注文を出し続けますが、保護的なテイクプロフィットは管理されません。
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Time-based breakout strategy. At start hour, detects breakout direction from previous candle range.
/// At stop hour, closes all positions.
/// </summary>
public class TwentyOneHourStrategy : Strategy
{
	private readonly StrategyParam<int> _startHour;
	private readonly StrategyParam<int> _stopHour;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _hasPrev;
	private bool _tradedToday;
	private int _lastTradeDay;

	public TwentyOneHourStrategy()
	{
		_startHour = Param(nameof(StartHour), 10)
			.SetDisplay("Start Hour", "Hour to look for breakout entries.", "Schedule");

		_stopHour = Param(nameof(StopHour), 22)
			.SetDisplay("Stop Hour", "Hour to close positions.", "Schedule");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candles used for time tracking.", "General");
	}

	public int StartHour
	{
		get => _startHour.Value;
		set => _startHour.Value = value;
	}

	public int StopHour
	{
		get => _stopHour.Value;
		set => _stopHour.Value = value;
	}

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

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevHigh = 0;
		_prevLow = 0;
		_hasPrev = false;
		_tradedToday = false;
		_lastTradeDay = -1;
	}

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

		_prevHigh = 0;
		_prevLow = 0;
		_hasPrev = false;
		_tradedToday = false;
		_lastTradeDay = -1;

		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;

		var hour = candle.OpenTime.Hour;
		var day = candle.OpenTime.DayOfYear;

		// Reset daily flag
		if (day != _lastTradeDay)
		{
			_tradedToday = false;
			_lastTradeDay = day;
		}

		// Close at stop hour
		if (hour >= StopHour && Position != 0)
		{
			if (Position > 0)
				SellMarket();
			else
				BuyMarket();
		}

		// Entry at start hour window
		if (hour >= StartHour && hour < StopHour && !_tradedToday && _hasPrev && Position == 0)
		{
			if (candle.ClosePrice > _prevHigh)
			{
				BuyMarket();
				_tradedToday = true;
			}
			else if (candle.ClosePrice < _prevLow)
			{
				SellMarket();
				_tradedToday = true;
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPrev = true;
	}
}