GitHub で見る

グローバル・ストップ・タイマー戦略

グローバル・ストップ・タイマー戦略は MetaTrader のエキスパート Exp_GStop_Tm から変換されたリスク管理オーバーレイである。 完成した各ローソク足でポートフォリオ価値を継続的に監視し、グローバルな利益目標または損失制限に達すると取引を停止する。 さらに、ユーザー定義の時間ウィンドウに取引を制限し、ウィンドウが閉じているときはすべてのオープンポジションを強制的にフラット化できる。

仕組み

  • 戦略が起動すると、初期ポートフォリオ残高を参照ポイントとして記録する。
  • サブスクライブされたローソク足シリーズが閉じるたびに、戦略は現在のポートフォリオ価値を読み取り、 初期残高との差を計算する。
  • 選択された StopCalculationMode に応じて、差はパーセンテージに変換されるか通貨として残される。
  • 損失が StopLoss を超えるか、利益が TakeProfit を超えると、戦略は停止状態に入り、 イベントをログに記録し、残りのポジションを閉じるための成行注文を送る。
  • オプションの取引ウィンドウが有効で現在の時刻がウィンドウを外れると、戦略もポジションを フラット化しようとする。ポジションサイズがゼロになると、ストップフラグがリセットされ、 次の有効なウィンドウ内で取引が再開できるようになる。

この戦略は自ら新規ポジションを開かない。他の戦略または手動取引を監視し、過度なドローダウンからアカウントを保護したり、 アカウント全体の利益を確保するように設計されている。

取引ウィンドウのロジック

取引ウィンドウはオリジナルのエキスパートロジックを再現する:

  • 開始時間が終了時間より小さい場合、同日の開始分(含む)から終了分(含まず)の間で取引が許可される。
  • 開始時間と終了時間が等しい場合、現在の分が StartMinute(含む)と EndMinute(含まず)の間にある場合のみ取引が許可される。
  • 開始時間が終了時間より大きい場合、セッションは深夜をまたぐ。取引は開始時刻から深夜まで有効で、 翌日は深夜から終了時刻まで再開される。

パラメーター

  • StopCalculationMode – パーセンテージベースまたは通貨ベースのグローバルストップを選択する。
  • StopLoss – グローバル損失閾値。パーセントモードがアクティブな場合はパーセンテージとして扱われ、それ以外はアカウント通貨として扱われる。
  • TakeProfit – グローバル利益目標。StopLoss と同じ単位を使用する。
  • UseTradingWindow – セッションフィルターを有効または無効にする。
  • StartHour / StartMinute – 許可された取引ウィンドウの開始時刻。
  • EndHour / EndMinute – 許可された取引ウィンドウの終了時刻。
  • CandleType – アカウント状態の評価頻度を定義するローソク足シリーズ。

注意事項

  • ストップチェックはローソク足のクローズ時に行われるため、迅速な反応が必要な場合は小さい時間軸(例えば1分)を使用する。
  • 戦略はこの戦略インスタンスが管理するポジションのみを決済する。複数の銘柄を個別に監視する必要がある場合は、別インスタンスを実行する。
  • 他の取引戦略と一緒に、親戦略として添付するか同じ銘柄で実行することで、アカウント全体の保護を提供する。
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>
/// Global Stop Timer strategy (simplified). Trades momentum with
/// time-based position management using EMA trend filter.
/// </summary>
public class GlobalStopTimerStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _momentumLength;
	private readonly StrategyParam<int> _emaLength;

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

	public int MomentumLength
	{
		get => _momentumLength.Value;
		set => _momentumLength.Value = value;
	}

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public GlobalStopTimerStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candles", "General");

		_momentumLength = Param(nameof(MomentumLength), 10)
			.SetGreaterThanZero()
			.SetDisplay("Momentum Length", "Momentum period", "Indicators");

		_emaLength = Param(nameof(EmaLength), 20)
			.SetGreaterThanZero()
			.SetDisplay("EMA Length", "EMA period", "Indicators");
	}

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

		var momentum = new Momentum { Length = MomentumLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		decimal prevMomentum = 100m;
		var hasPrev = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(momentum, ema, (ICandleMessage candle, decimal momVal, decimal emaVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!hasPrev)
				{
					prevMomentum = momVal;
					hasPrev = true;
					return;
				}

				if (!IsFormedAndOnlineAndAllowTrading())
				{
					prevMomentum = momVal;
					return;
				}

				var close = candle.ClosePrice;
				var bullishCross = prevMomentum <= 100m && momVal > 100m;
				var bearishCross = prevMomentum >= 100m && momVal < 100m;

				if (bullishCross && close > emaVal && Position <= 0)
					BuyMarket();
				else if (bearishCross && close < emaVal && Position >= 0)
					SellMarket();

				prevMomentum = momVal;
			})
			.Start();

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