GitHub で見る

ギャップ回復戦略

概要

ギャップ復帰戦略は、MetaTrader 4 エキスパート アドバイザー gaps.mq4 を直接移植したものです。システムは15分間のロウソクとトイレを監視します ks は、前のローソク足の高値/安値の範囲外で発生するギャップを開くためのものです。このようなギャップが現れた場合、すぐに戦略を立てることができます。 予想される平均回帰の方向に市場を誘導します。

StockSharp バージョンは、高レベルのキャンドル サブスクリプション API に依存しながら、元のロジックに従います。あらゆる貿易管理は、 成行注文で行われ、固定の保護注文は発注されません。これは、MQL コードで見られる動作を反映しています。

取引ルール

  1. 15 分のローソク足を購読します (CandleType パラメータで設定可能)。
  2. 前回完成したローソクの高値と安値を維持します。
  3. 新しいキャンドルが始まると:
    • ギャップ バッファを計算します: (MinGapSize + spreadInSteps) * pointValue。
    • 始値が previousHigh + gapBuffer を 上 にしている場合は、ショート ポジションをオープンします。
    • 始値が previousLow - gapBuffer を**下回る場合は、ロング ポジションをオープンします。
  4. キャンドルごとに 1 つの取引のみが許可されます。注文が出されると、ストラテジーは次のローソク足を待ってから新しい注文を生成します。 点火する。

スプレッド コンポーネントは、利用可能な場合、現在の最良の買値/売値を使用します。相場データが提供されない場合、戦略は罪に戻ります。 保守的なバッファーとしての gle 価格ステップ。

パラメーター

パラメータ デフォルト 説明
MinGapSize 1 注文を送信する前に超える必要がある価格ステップの最小ギャップ サイズ。
GapVolume 0.1 ギャップによって引き起こされる市場エントリーの注文量。
CandleType 15m TimeFrame 計算に使用されるローソク足のタイプ (デフォルトは 15 分足ローソク足)。

すべてのパラメータは StrategyParam<T> として登録され、StockSharp デザイナーまたはその他のツール内での最適化をサポートします。

実装メモ

  • SubscribeCandles を Bind とともに使用して、完成したキャンドルのみを処理します。
  • データ系列の再計算を避けるために、前のローソク足の範囲を記憶します。
  • 取引をトリガーしたバーのオープン時間を追跡することで、同じローソク足での重複注文をブロックします。
  • チャート出力は登録されたローソク足を描画し、戦略は迅速な視覚的検査のために取引されます。

MQL バージョンとの違い

  • 元の EA でテイクプロフィットとストップロスのレベルが正しく設定されていませんでした (MQL コードが間違ったパラメータに値を渡しました) 。したがって、StockSharp ポートは、保護命令なしで実行される動作を維持します。
  • スプレッド処理は、利用可能な場合にリアルタイムの買値/売値をチェックするようになり、より適応性の高いバッファーを提供します。

要件

  • StockSharp API は、選択した商品のローソク足データにアクセスできます。
  • レベル 1 の引用はオプションですが、スプレッドの検出が向上します。

リプレイ時の判定時刻: 各足の最初の更新(提供される場合は Active)でギャップを判定します。Finished の足しか配信されない場合、始値ギャップの検出は足の確定時となり、始値で約定したことの検証にはなりません。

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// Enters against gaps between a new candle's open and the previous completed range.
/// No EMA condition or automatic protection is part of the original gap rules.
/// </summary>
public class GapReversionStrategy : Strategy
{
    private readonly StrategyParam<decimal> _minGapSize;
    private readonly StrategyParam<decimal> _gapVolume;
    private readonly StrategyParam<DataType> _candleType;

    private decimal? _previousHigh;
    private decimal? _previousLow;
    private DateTimeOffset? _observedBar;
    private decimal? _bid;
    private decimal? _ask;

    public decimal MinGapSize { get => _minGapSize.Value; set => _minGapSize.Value = value; }
    public decimal GapVolume { get => _gapVolume.Value; set => _gapVolume.Value = value; }
    public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

    public GapReversionStrategy()
    {
        _minGapSize = Param(nameof(MinGapSize), 1m)
            .SetGreaterThanZero()
            .SetDisplay("Minimum Gap", "Minimum open gap in price steps", "Signals");
        _gapVolume = Param(nameof(GapVolume), 0.1m)
            .SetGreaterThanZero()
            .SetDisplay("Gap Volume", "Volume of each market gap entry", "Trading");
        _candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
            .SetDisplay("Candle Type", "Candle timeframe", "General");
    }

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

    protected override void OnReseted()
    {
        base.OnReseted();
        ClearState();
    }

    private void ClearState()
    {
        _previousHigh = null;
        _previousLow = null;
        _observedBar = null;
        _bid = null;
        _ask = null;
    }

    protected override void OnStarted2(DateTime time)
    {
        base.OnStarted2(time);
        ClearState();
        Volume = GapVolume;

        // Quote subscriptions are optional: one price step is used as spread
        // if the replay or provider does not expose both best prices.
        foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
        {
            var quotes = new Subscription(DataType.Level1, Security);
            quotes.MarketData.BuildField = field;
            SubscribeLevel1(quotes).Bind(ProcessQuote).Start();
        }

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

    private void ProcessQuote(Level1ChangeMessage quote)
    {
        if (quote.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid)
            _bid = bid;
        if (quote.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask)
            _ask = ask;
    }

    private void ProcessCandle(ICandleMessage candle)
    {
        if (candle.State is not (CandleStates.Active or CandleStates.Finished))
            return;

        // Process a bar just once, at its first update (Active if available).
        // Finished-only feeds can only evaluate the opening gap retrospectively.
        if (_observedBar != candle.OpenTime)
        {
            _observedBar = candle.OpenTime;
            if (_previousHigh is decimal high && _previousLow is decimal low)
            {
                var step = Security?.PriceStep ?? 0m;
                if (step > 0m)
                {
                    var spread = _bid is decimal bid && _ask is decimal ask && ask > bid
                        ? ask - bid
                        : step;
                    var buffer = MinGapSize * step + spread;
                    if (candle.OpenPrice > high + buffer)
                        SellMarket(GapVolume);
                    else if (candle.OpenPrice < low - buffer)
                        BuyMarket(GapVolume);
                }
            }
        }

        if (candle.State == CandleStates.Finished)
        {
            _previousHigh = candle.HighPrice;
            _previousLow = candle.LowPrice;
        }
    }
}