GitHub で見る

SurefireThing 戦略

概要

SurefireThing 戦略は、MetaTrader 4 エキスパート アドバイザー Surefirething の StockSharp の高レベル移植です。これは完了したローソク足で動作し、前のセッション範囲から未決注文レベルを計算し、各取引日の変わり目にエクスポージャーをリセットします。このロジックは、前の終値付近での平均反転を捉えようとする対称的な指値注文のペアを展開することに中心を置いています。

取引ロジック

  • 各取引日の終了時に、この戦略はポジションをフラット化しようとし、アクティブな未決注文をキャンセルします。
  • 前日の最後に完了したローソク足を使用して、価格範囲 (High - Low) を測定し、それに RangeMultiplier を乗算します (デフォルトは元の EA と同様に 1.1)。
  • 調整されたレンジの半分が前の終値に追加され、売り指値のエントリー価格が得られます。同じ距離が終値から差し引かれ、買い指値注文が出されます。
  • ストップロスとテイクプロフィットのオフセットは価格ステップで表されます。機器が有効な Security.Step を公開すると、それらは絶対距離に変換され、StartProtection を通じて管理されるため、埋まったポジションは自動的に保護命令を受けます。
  • 注文は取引日ごとに 1 回送信されます。フィルが発生すると、接続されている保護ハンドルが終了します。それ以外の場合、注文は次の毎日のリセットまでアクティブなままになります。

パラメーター

パラメータ 説明 デフォルト
OrderVolume 各未決注文とともに送信された数量。 0.1
TakeProfitPoints 利益目標までの距離 (価格ステップ)。ステップが既知の場合は、絶対オフセットに変換されます。 10
StopLossPoints 保護停止までの距離 (価格ステップ)。利益目標と同様に換算します。 15
RangeMultiplier エントリー価格を計算する前に、前のローソク足の範囲に適用される係数。 1.1
CandleType 戦略によって処理される主な時間枠。デフォルトは 1 分足のローソク足ですが、元のチャートに合わせて調整できます。 TimeSpan.FromMinutes(1)

実装メモ

  • 高レベルの API: キャンドルは SubscribeCandles(CandleType) を通じて消費され、終了すると ProcessCandle ハンドラーで処理されます。
  • 毎日のリセット: CloseForNewDay は、ローソク足のタイムスタンプから新しい暦日が検出されるたびに未決注文をキャンセルし、ポジションを閉じます。
  • 保護ロジック: ConfigureProtection は、ポイントベースのリスク制御を Unit インスタンスに変換し、StartProtection をアクティブ化します。これにより、約定後にストップロスとテイクプロフィット注文が自動的に再作成されます。
  • 注文のライフサイクル: 両方の保留中の注文への参照は、注文が終了またはキャンセルされると、CancelPendingOrderOnOrderChanged を介して保存および消去されます。
  • 価格の正規化: Security.ShrinkPrice は、新しい注文を送信する前に、計算された価格を商品ティック サイズに丸めるために使用されます。

使用上の推奨事項

  • 同じ基準ローソクを維持するために、CandleType を元の EA (通常はそれが添付されているチャート) で使用されているタイムフレームに合わせます。
  • 商品が異なるボラティリティ特性を示す場合は、未決注文が現実的な距離内に留まるように、RangeMultiplier を調整します。
  • ブローカーが最小停止距離を強制する場合は、絶対価格に変換した後、TakeProfitPointsStopLossPoints がこれらの制約を遵守するようにしてください。
  • この戦略は、継続的な日中データを前提としています。大きなギャップが発生した場合(週末、休日)、次に利用可能なローソク足が最後に観察されたバーに基づいてリセットと新しい注文の発注をトリガーします。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Daily range breakout strategy.
/// Calculates buy/sell levels from previous day's range.
/// Buys when price drops below the lower level, sells when above the upper level.
/// Closes position at end of day.
/// </summary>
public class SurefireThingStrategy : Strategy
{
	private readonly StrategyParam<decimal> _rangeMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private DateTime? _currentDay;
	private decimal _buyLevel;
	private decimal _sellLevel;
	private bool _levelsReady;
	private decimal _prevDayClose;
	private decimal _prevDayHigh;
	private decimal _prevDayLow;
	private decimal _dayHigh;
	private decimal _dayLow;
	private decimal _dayClose;
	private bool _hasPrevDay;
	private bool _tradedToday;

	public decimal RangeMultiplier
	{
		get => _rangeMultiplier.Value;
		set => _rangeMultiplier.Value = value;
	}

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

	public SurefireThingStrategy()
	{
		_rangeMultiplier = Param(nameof(RangeMultiplier), 0.5m)
			.SetDisplay("Range Mult", "Multiplier for range-based levels", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Candle series", "General");
	}

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

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

		_currentDay = null;
		_buyLevel = 0m;
		_sellLevel = 0m;
		_levelsReady = false;
		_prevDayClose = 0m;
		_prevDayHigh = 0m;
		_prevDayLow = 0m;
		_dayHigh = 0m;
		_dayLow = 0m;
		_dayClose = 0m;
		_hasPrevDay = false;
		_tradedToday = false;
	}

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

		_currentDay = null;
		_levelsReady = false;
		_hasPrevDay = false;
		_tradedToday = false;

		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 day = candle.OpenTime.Date;

		// New day detected
		if (_currentDay == null || day > _currentDay.Value)
		{
			// Close position at end of previous day
			if (Position > 0)
				SellMarket();
			else if (Position < 0)
				BuyMarket();

			// Save previous day stats
			if (_currentDay != null)
			{
				_prevDayClose = _dayClose;
				_prevDayHigh = _dayHigh;
				_prevDayLow = _dayLow;
				_hasPrevDay = true;
			}

			// Calculate new levels
			if (_hasPrevDay)
			{
				var range = _prevDayHigh - _prevDayLow;
				if (range > 0)
				{
					var halfRange = range * RangeMultiplier;
					_buyLevel = _prevDayClose - halfRange;
					_sellLevel = _prevDayClose + halfRange;
					_levelsReady = true;
				}
			}

			_currentDay = day;
			_dayHigh = candle.HighPrice;
			_dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
			_tradedToday = false;
		}
		else
		{
			if (candle.HighPrice > _dayHigh) _dayHigh = candle.HighPrice;
			if (candle.LowPrice < _dayLow) _dayLow = candle.LowPrice;
			_dayClose = candle.ClosePrice;
		}

		if (!_levelsReady)
			return;

		var price = candle.ClosePrice;

		// Only one trade per day per direction
		if (!_tradedToday && Position == 0)
		{
			if (price <= _buyLevel)
			{
				BuyMarket();
				_tradedToday = true;
			}
			else if (price >= _sellLevel)
			{
				SellMarket();
				_tradedToday = true;
			}
		}
	}
}