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
Abonnieren Sie 15-Minuten-Kerzen (konfigurierbar über den Parameter CandleType).
Behalten Sie das Hoch und Tief der zuvor abgeschlossenen Kerze bei.
Wenn eine neue Kerze beginnt:
Berechnen Sie den Lückenpuffer: (MinGapSize + spreadInSteps) * pointValue.
Wenn der Eröffnungspreis überpreviousHigh + gapBuffer liegt, eröffnen Sie eine Short-Position.
Wenn der Eröffnungspreis unterpreviousLow - gapBuffer liegt, eröffnen Sie eine Long-Position.
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;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class gap_reversion_strategy(Strategy):
"""Trades against opening gaps beyond the previous candle's high/low and spread."""
def __init__(self):
super(gap_reversion_strategy, self).__init__()
self._min_gap_size = self.Param("MinGapSize", 1.0).SetGreaterThanZero().SetDisplay(
"Minimum Gap", "Minimum open gap in price steps", "Signals")
self._gap_volume = self.Param("GapVolume", 0.1).SetGreaterThanZero().SetDisplay(
"Gap Volume", "Volume of each market gap entry", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay(
"Candle Type", "Candle timeframe", "General")
self._clear_state()
@property
def min_gap_size(self):
return self._min_gap_size.Value
@property
def gap_volume(self):
return self._gap_volume.Value
@property
def candle_type(self):
return self._candle_type.Value
def _clear_state(self):
self._previous_high = None
self._previous_low = None
self._observed_bar = None
self._best_bid = None
self._best_ask = None
def OnReseted(self):
super(gap_reversion_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(gap_reversion_strategy, self).OnStarted2(time)
self._clear_state()
self.Volume = Decimal(self.gap_volume)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._process_quote).Start()
self.SubscribeCandles(self.candle_type).Bind(self._process_candle).Start()
def _process_quote(self, quote):
bid = quote.Changes[Level1Fields.BestBidPrice] if quote.Changes.ContainsKey(Level1Fields.BestBidPrice) else None
ask = quote.Changes[Level1Fields.BestAskPrice] if quote.Changes.ContainsKey(Level1Fields.BestAskPrice) else None
if bid is not None:
self._best_bid = float(bid)
if ask is not None:
self._best_ask = float(ask)
def _process_candle(self, candle):
if candle.State != CandleStates.Active and candle.State != CandleStates.Finished:
return
# Active updates allow execution at the open. Finished-only feeds necessarily
# discover the opening gap at close; this is not equivalent to open execution.
if self._observed_bar != candle.OpenTime:
self._observed_bar = candle.OpenTime
if self._previous_high is not None and self._previous_low is not None:
step = float(self.Security.PriceStep)
if step > 0.0:
spread = (self._best_ask - self._best_bid
if self._best_ask is not None and self._best_bid is not None
and self._best_ask > self._best_bid else step)
buffer = float(self.min_gap_size) * step + spread
op = float(candle.OpenPrice)
if op > self._previous_high + buffer:
self.SellMarket(Decimal(self.gap_volume))
elif op < self._previous_low - buffer:
self.BuyMarket(Decimal(self.gap_volume))
if candle.State == CandleStates.Finished:
self._previous_high = float(candle.HighPrice)
self._previous_low = float(candle.LowPrice)
def CreateClone(self):
return gap_reversion_strategy()