GitHub で見る

毎日の STP エントリー フレーム戦略 (StockSharp)

概要

デイリー STP エントリー フレーム戦略 は、StockSharp の高レベル API を使用して、元の MetaTrader エキスパート アドバイザー「デイリー STP エントリー フレーム」の動作を複製します。システムは、新しい取引日の開始時にブレイクアウトストップ注文を準備します。エントリー価格は前日の高値と安値から導出され、追加のフィルターを使用して、注文を準備する前に市場がこれらの極端な値付近に位置していることを確認します。このロジックは、「ベースポイント」が 5 桁の相場の 1 ピップの 10 分の 1 に相当する外国為替スタイルの商品向けに調整されています。

コアワークフロー

  1. 毎日のレンジ追跡 – この戦略は、毎日のローソク足をサブスクライブして、前のセッションの高値と安値を記憶します。
  2. リアルタイム監視 – レベル 1 データは、日中管理のための現在の買値、売値、および最後の取引価格を提供します。
  3. 注文の準備 – 新しい日の始まりに、最後の価格が昨日の高値/安値から少なくとも ThresholdPoints 離れていて、当日の始値がその極端な値の正しい側にある場合、逆指値注文が送信されます。
    • High + SpreadPoints / 2 でストップを購入します (価格単位に変換)。
    • Low - SpreadPoints / 2での販売ストップ。
  4. リスク検証 – 資本ドローダウンが MaximumDrawdownPercent を超えるか、時間フィルター (週末、時間フィルター、または日フィルター) で取引が禁止されている場合、新しい注文はブロックされます。
  5. ポジション管理 – 取引がアクティブになると、戦略により次のことが強制されます。
    • 静的なストップロスとテイクプロフィットの距離。
    • CloseAfterSeconds の後のオプションの時間ベースの終了。
    • オリジナルの「SL スロープ」パラメータをエミュレートするオプションのトレーリング ストップ。
  6. 一日の終わりの衛生管理 – 保留中の注文は、NoNewOrdersHour (または専用の金曜日の締め切り時間) 以降、および暦日が変わるたびにキャンセルされます。

取引ルール

  • 長いエントリ
    • SideFilter0 (両方) または 1 (ロングのみ) の場合に許可されます。
    • 前日の高値から現在の価格を引いた値 ≥ ThresholdPoints
    • 今日の始値は昨日の高値を下回っています。
    • 計算されたエントリー価格は、現在のアスクからの最小距離を考慮する必要があります。
  • 短いエントリー
    • SideFilter0 (両方) または -1 (短いのみ) の場合に許可されます。
    • 現在の価格から前日の安値を引いた値 ≥ ThresholdPoints
    • 今日の始値は昨日の安値を上回っています。
    • 計算されたエントリー価格は、現在の入札からの最小距離を考慮する必要があります。
  • お金の管理
    • 動的なボリューム サイジングでは、累積利益 (PercentOfProfit) の割合が使用されます。
    • 結果のサイズは MinVolumeMaxVolume によって制限され、機器の VolumeStep に合わせられます。
    • 測定されたドローダウンが MaximumDrawdownPercent に達すると、取引は自動的に一時停止されます。
  • 保護ロジック
    • ストップロスとテイクプロフィットのレベルはベースポイントで表され、商品のピップサイズを使用して価格オフセットに変換されます。
    • トレーリングストップは、TrailingSlope < 1 の場合にのみアクティブになります。未実現利益が増加するにつれて、保護しきい値を価格に近づけます。
    • ライフタイム出口は、設定された秒数が経過すると、開いているポジションを閉じます。

パラメーター

名前 説明
CandleType 参照ローソク足を取得するために使用される時間枠 (デフォルトでは毎日)。
StopLossPoints ベースポイントでのストップロス距離。
TakeProfitPoints ベースポイントでのテイクプロフィット距離。
TrailingSlope トレーリング中に利益の一部が留保される。 ≥ 1 はこの機能を無効にします。
SideFilter -1 ショートのみ、0 両方向、1 ロングのみ。
ThresholdPoints 現在の価格とストップを設定するために必要な以前の極値との間の最小ギャップ。
SpreadPoints スプレッドを補正するための追加のオフセット (極端値の上/下で半分が使用されます)。
SlippagePoints 最小停止距離チェックに安全バッファが追加されました。
NoNewOrdersHour 通常の日に保留中の注文をキャンセルする時間 (プラットフォーム時間)。
NoNewOrdersHourFriday 金曜日特有のキャンセル時間。
EarliestOrderHour 新しい注文を作成できる 1 日の最も早い時間。
DayFilter 全日の場合は 6、日曜日から金曜日までの取引の場合は 0 ~ 5。
CloseAfterSeconds オプションの時間ベースの終了 (0 は無効)。
PercentOfProfit ポジションサイズを調整するために使用される累積利益の割合。
MinVolume / MaxVolume 計算されたボリュームのハード境界。
MaximumDrawdownPercent 新しい注文をブロックするドローダウンしきい値。

変換メモ

  • Pip 変換は MetaTrader の実装を反映します。セキュリティが小数点以下 3 桁または 5 桁を公開する場合、基点は PriceStep * 10 になります。
  • 逆指値注文のキャンセルウィンドウは、金曜日の別の締め切りを含む、専門家の夕方の清掃を再現します。
  • トレーリング ロジックは元の勾配公式 (ロングの場合は newStop = Bid - StopLoss - Slope * (Bid - Entry)) に従います。
  • MQL バージョンからの株式通知は戦略ログ メッセージに置き換えられます。
  • StockSharp 実装は、ソースの動作と一致して、ポジションがオープンしている場合でも保留中の注文をアクティブに保ちます。

使用のヒント

  • 正確なサイジングを確保するために、適切に構成された PriceStepStepPrice、および VolumeStep の値を外国為替商品に割り当てます。
  • ライブ実行時には、この戦略と StockSharp のリスク管理 (ポートフォリオ制限、コネクタ レベルの保護) を組み合わせます。
  • デザイナーまたはランナーを使用して ThresholdPointsTrailingSlope、および PercentOfProfit を最適化し、特定のシンボルに対するブレイクアウト感度を調整します。
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>
/// Daily breakout strategy that enters on price crossing previous session extremes.
/// Converted from "Daily STP Entry Frame" MetaTrader expert.
/// Uses market orders when price breaks above previous high or below previous low.
/// </summary>
public class DailyStpEntryFrameStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;

	private decimal _pipSize;
	private decimal _stopLossOffset;
	private decimal _takeProfitOffset;

	private decimal? _previousDayHigh;
	private decimal? _previousDayLow;
	private decimal _currentDayHigh;
	private decimal _currentDayLow;
	private DateTime? _currentTradingDay;
	private bool _tradedToday;

	private decimal _entryPrice;
	private decimal? _longStop;
	private decimal? _longTake;
	private decimal? _shortStop;
	private decimal? _shortTake;

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

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

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

	public DailyStpEntryFrameStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Time-frame for monitoring", "General");

		_stopLossPoints = Param(nameof(StopLossPoints), 80m)
			.SetNotNegative()
			.SetDisplay("Stop-Loss (points)", "Stop-loss distance", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
			.SetNotNegative()
			.SetDisplay("Take-Profit (points)", "Take-profit distance", "Risk");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_pipSize = 0m;
		_stopLossOffset = 0m;
		_takeProfitOffset = 0m;
		_previousDayHigh = null;
		_previousDayLow = null;
		_currentDayHigh = 0m;
		_currentDayLow = 0m;
		_currentTradingDay = null;
		_tradedToday = false;
		_entryPrice = 0m;
		_longStop = null;
		_longTake = null;
		_shortStop = null;
		_shortTake = null;
	}

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

		_pipSize = Security?.PriceStep ?? 0.01m;
		if (_pipSize <= 0) _pipSize = 0.01m;

		_stopLossOffset = StopLossPoints * _pipSize;
		_takeProfitOffset = TakeProfitPoints * _pipSize;

		_previousDayHigh = null;
		_previousDayLow = null;
		_currentTradingDay = null;
		_tradedToday = false;

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

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

		var date = candle.OpenTime.Date;

		// Track daily high/low
		if (_currentTradingDay != date)
		{
			// Save previous day's range
			if (_currentTradingDay != null)
			{
				_previousDayHigh = _currentDayHigh;
				_previousDayLow = _currentDayLow;
			}

			_currentTradingDay = date;
			_currentDayHigh = candle.HighPrice;
			_currentDayLow = candle.LowPrice;
			_tradedToday = false;
		}
		else
		{
			_currentDayHigh = Math.Max(_currentDayHigh, candle.HighPrice);
			_currentDayLow = Math.Min(_currentDayLow, candle.LowPrice);
		}

		// Manage existing position
		ManagePosition(candle);

		// Check for breakout entries
		if (_previousDayHigh is null || _previousDayLow is null)
			return;

		if (_tradedToday || Position != 0)
			return;

		var close = candle.ClosePrice;

		// Breakout above previous day high => buy
		if (close > _previousDayHigh.Value)
		{
			_entryPrice = close;
			_longStop = _stopLossOffset > 0 ? close - _stopLossOffset : null;
			_longTake = _takeProfitOffset > 0 ? close + _takeProfitOffset : null;
			_shortStop = null;
			_shortTake = null;
			BuyMarket();
			_tradedToday = true;
		}
		// Breakout below previous day low => sell
		else if (close < _previousDayLow.Value)
		{
			_entryPrice = close;
			_shortStop = _stopLossOffset > 0 ? close + _stopLossOffset : null;
			_shortTake = _takeProfitOffset > 0 ? close - _takeProfitOffset : null;
			_longStop = null;
			_longTake = null;
			SellMarket();
			_tradedToday = true;
		}
	}

	private void ManagePosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (_longStop is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
				_longStop = null;
				_longTake = null;
				return;
			}
			if (_longTake is decimal take && candle.HighPrice >= take)
			{
				SellMarket();
				_longStop = null;
				_longTake = null;
			}
		}
		else if (Position < 0)
		{
			if (_shortStop is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
				_shortStop = null;
				_shortTake = null;
				return;
			}
			if (_shortTake is decimal take && candle.LowPrice <= take)
			{
				BuyMarket();
				_shortStop = null;
				_shortTake = null;
			}
		}
	}
}