GitHub で見る

スプレッドデータコレクター戦略

概要

スプレッド データ コレクター戦略は、MetaTrader 5 ユーティリティ「スプレッド データ コレクター」の StockSharp ポートです (MQL エントリ 33314)。元のエキスパートアドバイザーは注文を出しません。代わりに、買い/売りストリームをリッスンし、事前定義されたスプレッド範囲内に何ティックが入るかをカウントします。取引年が変わるか、エキスパートが停止するたびに、統計の概要が出力されます。この C# バージョンは、高レベルの SubscribeLevel1() API を使用して同じ動作を再現し、範囲のしきい値を構成可能なパラメーターとして公開します。

操作内容

  • この戦略は、開始時にメインの Security のレベル 1 (入札/売値) 更新をサブスクライブします。
  • 入札価格と売値の両方が利用可能になるたびに、戦略はスプレッドを計算し、構成されたポイント制限に Security.PriceStep を乗算して価格単位に変換します。
  • 6 つのカウンターが維持されます。
    1. 最初のしきい値を厳密に下回って拡散します。
    2. 最初のしきい値と 2 番目のしきい値の間に広がります。
    3. 2 番目と 3 番目のしきい値の間に広がります。
    4. 3 番目と 4 番目のしきい値の間に広がります。
    5. 4 番目と 5 番目のしきい値の間に広がります。
    6. 5 番目のしきい値を超えるスプレッド。
  • 年の推移は交換タイムスタンプ (Level1ChangeMessage.ServerTime) から検出されます。年が切り替わると、ストラテジーは終了した年の概要を出力し、カウンターをリセットします。
  • ストラテジーが停止すると、シャットダウンする前に今年の統計が出力されます。

このポートは、MQL ユーティリティのロギングのみの性質を維持し、トレーダーが注文を送信したりポジションを操作したりすることなく、さまざまな期間にスプレッドがどのように動作したかを分析できるようにします。

パラメーター

すべての入力は ポイント (MetaTrader の用語) で表されます。実際の価格距離は points × Security.PriceStep として計算されます。

パラメータ デフォルト 説明
FirstBucketPoints 10 最初のスプレッド バケットの上限。この制限を厳密に下回るスプレッドは、最初のカテゴリにカウントされます。
SecondBucketPoints 20 2 番目のスプレッド バケットの上限。 [FirstBucketPoints, SecondBucketPoints) のスプレッドはここでカウントされます。
ThirdBucketPoints 30 3 番目のスプレッド バケットの上限。 [SecondBucketPoints, ThirdBucketPoints) のスプレッドにより、このカウンタが増加します。
FourthBucketPoints 40 4 番目のスプレッド バケットの上限。 [ThirdBucketPoints, FourthBucketPoints) のスプレッドがここに記録されます。
FifthBucketPoints 50 5 番目のスプレッド バケットの上限。 [FourthBucketPoints, FifthBucketPoints) のスプレッドにより、このカウンタが増加します。

すべてのしきい値は厳密に増加する必要があります。無効または非正の Security.PriceStep 値を使用して戦略を開始しようとすると、実行時例外が発生し、一貫性のない統計からユーザーが保護されます。

ログと出力

統計は、AddInfoLog を通じて次の形式で出力されます。

「」 年=2024 スプレッド<=10ポイント=15342 Spread_10_20pts=2841 Spread_20_30pts=912 ... スプレッド>50ポイント=37 「」

この出力は、MetaTrader エキスパートの Print ステートメントを反映しているため、両方の環境を簡単に比較できます。 StockSharp ログ ビューアを使用するか、ログをファイルにリダイレクトしてさらに分析します。

使用上のチェックリスト

  1. ターゲット商品を Strategy.Security に割り当て、その PriceStep が MetaTrader ポイント サイズと一致することを確認します (ほとんどの外国為替シンボルでは、これは 0.0001 に等しい)。
  2. 異なるスプレッド範囲が必要な場合は、バケットのしきい値を調整します。値は厳密に昇順にしてください。
  3. 戦略を開始し、実行しましょう。注文は送信されません。
  4. 年次ログを確認して、セッション間の拡散動作を理解します。

この戦略は意図的に軽量化されており、ライブ取引システムと並行して安全に実行できます。これは、デスクが過去のスプレッド分布を構築し、流動性の仮定を検証し、ブローカーの状態を長期にわたって監視するのに役立ちます。

namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Spread Data Collector strategy: RSI mean reversion.
/// Buys when RSI crosses below oversold, sells when RSI crosses above overbought.
/// </summary>
public class SpreadDataCollectorStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;

	private decimal _prevRsi;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }

	public SpreadDataCollectorStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "RSI oversold level", "Levels");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "RSI overbought level", "Levels");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ProcessCandle).Start();
	}

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

		if (_hasPrev)
		{
			if (_prevRsi >= Oversold && rsiValue < Oversold && Position <= 0)
				BuyMarket();
			else if (_prevRsi <= Overbought && rsiValue > Overbought && Position >= 0)
				SellMarket();
		}

		_prevRsi = rsiValue;
		_hasPrev = true;
	}
}