Auf GitHub ansehen

Gap-Reversion-Strategie

Überblick

Die Gap-Reversion-Strategie ist eine direkte Portierung des MetaTrader 4 Expertenberaters gaps.mq4. Das System überwacht 15-Minuten-Kerzen und Toilette ks für das Öffnen von Lücken, die außerhalb des Hoch-/Tief-Bereichs der vorherigen Kerze auftreten. Wenn eine solche Lücke auftritt, wird die Strategie sofort, z Bringt den Markt in die Richtung der erwarteten Mean-Reversion-Bewegung.

Die StockSharp-Version folgt der ursprünglichen Logik und stützt sich dabei auf das High-Level-Kerzenabonnement API. Das gesamte Handelsmanagement ist Dies erfolgt mit Marktaufträgen und es werden keine festen Schutzaufträge erteilt, was das Verhalten im MQL-Code widerspiegelt.

Handelsregeln

  1. Abonnieren Sie 15-Minuten-Kerzen (konfigurierbar über den Parameter CandleType).
  2. Behalten Sie das Hoch und Tief der zuvor abgeschlossenen Kerze bei.
  3. Wenn eine neue Kerze beginnt:
    • Berechnen Sie den Lückenpuffer: (MinGapSize + spreadInSteps) * pointValue.
    • Wenn der Eröffnungspreis über previousHigh + gapBuffer liegt, eröffnen Sie eine Short-Position.
    • Wenn der Eröffnungspreis unter previousLow - gapBuffer liegt, eröffnen Sie eine Long-Position.
  4. Es ist nur ein Trade pro Kerze erlaubt. Sobald eine Order aufgegeben wurde, wartet die Strategie auf die nächste Kerze, bevor sie eine neue Kerze generiert ignal.

Die Spread-Komponente verwendet den aktuell besten Geld-/Briefkurs, sofern verfügbar. Wenn keine Kursdaten bereitgestellt werden, fällt die Strategie auf eine Sünde zurück gle-Preisschritt als konservativer Puffer.

Parameter

Parameter Standard Beschreibung
MinGapSize 1 Mindestlückengröße in Preisschritten, die vor dem Absenden einer Bestellung überschritten werden muss.
GapVolume 0.1 Auftragsvolumen für durch Lücken ausgelöste Markteintritte.
CandleType 15m TimeFrame Für Berechnungen verwendeter Kerzentyp (standardmäßig 15-Minuten-Kerzen).

Alle Parameter sind als StrategyParam<T> registriert und unterstützen die Optimierung im StockSharp Designer oder anderen Tools.

Implementierungshinweise

  • Verwendet SubscribeCandles mit Bind, um nur fertige Kerzen zu verarbeiten.
  • Merkt sich den Bereich der vorherigen Kerze, um eine Neuberechnung der Datenreihen zu vermeiden.
  • Blockiert doppelte Orders auf derselben Kerze, indem die Öffnungszeit des Balkens verfolgt wird, der den Handel ausgelöst hat.
  • Die Diagrammausgabe zeichnet die abonnierten Kerzen und die Strategiegeschäfte zur schnellen visuellen Überprüfung.

Unterschiede zur MQL-Version

  • Take-Profit- und Stop-Loss-Level wurden im ursprünglichen EA nicht richtig festgelegt (der MQL-Code hat Werte an die falschen Parameter übergeben) . Der Port StockSharp behält daher das Verhalten bei, dass er ohne Schutzbefehle ausgeführt wird.
  • Bei der Spread-Verarbeitung werden jetzt Geld-/Briefkurse in Echtzeit überprüft, sofern verfügbar, wodurch ein anpassungsfähigerer Puffer bereitgestellt wird.

Anforderungen

  • StockSharp API mit Zugriff auf Kerzendaten für das ausgewählte Instrument.
  • Kurse der Stufe 1 sind optional, verbessern jedoch die Spread-Erkennung.

Zeitpunkt beim Replay: Die Kurslücke wird beim ersten Update der Kerze geprüft (Active, sofern verfügbar). Liefert die Datenquelle nur Finished-Kerzen, wird die Eröffnungslücke erst beim Kerzenschluss erkannt; dies bestätigt keine Ausführung zum Eröffnungskurs.

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;
        }
    }
}