Diese Strategie repliziert die Logik des MetaTrader-Experten "Tunnel gen4" mithilfe der StockSharp-High-Level-API. Sie hält eine marktneutrale Absicherung aufrecht, indem ein erstes Kauf-/Verkaufspaar eröffnet wird, die Position in Richtung des Ausbruchs verdoppelt wird, sobald der Preis eine konfigurierbare Anzahl von Pips zurücklegt, und die gesamte Basket-Position geschlossen wird, wenn dieselbe Distanz über den zweiten Anker hinaus zurückgelegt wird.
Handelslogik
Anfängliche Absicherung: Sobald keine Exposition besteht, sendet die Strategie gleichzeitige Market-Buy- und Sell-Orders mit dem Volumen StartVolume. Die erste Ausführung definiert den Referenzpreis für alle nachfolgenden Entscheidungen.
Schritt-Erkennung: Das konfigurierte StepPips wird mit der Tick-Größe des Instruments in ein Preisoffset umgerechnet (mit automatischen Anpassungen für Forex-Notierungen mit drei und fünf Dezimalstellen). Best-Bid/Ask-Aktualisierungen aus dem Level-1-Stream werden mit diesem Offset verglichen.
Verstärkungsorder: Wenn der beste Bid mindestens einen Schritt vom ersten Fill steigt, wird eine Verkaufsorder mit dem doppelten Basisvolumen gesendet. Wenn der beste Ask mindestens einen Schritt fällt, wird stattdessen eine Kauforder derselben Größe ausgegeben. Der erste Fill dieser Order wird zum zweiten Anker.
Zyklusbeendigung: Nachdem der zweite Anker aktiv ist, löst jede weitere schrittgroße Bewegung in eine Richtung eine vollständige Liquidation aller offenen Positionen aus. Sobald beide Seiten geschlossen sind, setzt sich der Zustand zurück und ein neuer Zyklus kann beginnen.
Volumenvalidierung: Der Strategiestart überprüft, dass sowohl das anfängliche als auch das verdoppelte Volumen die Mindest-, Maximal- und Schrittanforderungen des Instruments erfüllen, sodass jede an den Connector gesendete Order ausführbar ist.
Einstiegsbedingungen
Long-Verstärkung
Es gibt mindestens eine offene Position aus der anfänglichen Absicherung.
Der zweite Anker wurde noch nicht erstellt.
Der aktuelle beste Ask-Preis ist kleiner oder gleich first_fill_price - StepPips_in_price.
Short-Verstärkung
Es gibt mindestens eine offene Position aus der anfänglichen Absicherung.
Der zweite Anker wurde noch nicht erstellt.
Der aktuelle beste Bid-Preis ist größer oder gleich first_fill_price + StepPips_in_price.
Ausstiegsmanagement
Basket-Schließung: Sobald der zweite Anker definiert ist, werden Marktorders eingereicht, um die kumulierte Long- und Short-Exposition zu schließen, wenn der beste Bid über second_anchor + StepOffset steigt oder der beste Ask unter second_anchor - StepOffset fällt. Schließungsorders werden verfolgt, um sicherzustellen, dass der Zustand erst nach der Bestätigung aller Trades zurückgesetzt wird.
Zustandsreset: Nachdem beide Seiten geschlossen sind und keine Schließungsorders mehr aktiv sind, löscht die Strategie die internen Anker und wartet auf eine neue Absicherungseröffnung.
Daten und Indikatoren
Das Level-1-Abonnement liefert die besten Bid- und Ask-Preise, die für Schrittvergleiche verwendet werden.
Es sind keine zusätzlichen Indikatoren erforderlich; die gesamte Logik arbeitet auf rohen Kurs-Updates.
Die Preisschritt-Konvertierung imitiert die MetaTrader-Punkt-zu-Pip-Anpassung, sodass Forex-Symbole mit drei oder fünf Dezimalstellen das gleiche Verhalten wie im Quell-Experten zeigen.
Parameter
Parameter
Beschreibung
StartVolume
Volumen der Kauf- und Verkaufsorders, die die anfängliche Absicherung bilden.
StepPips
Abstand in Pips, der die Verstärkungsorder und den anschließenden Basket-Ausstieg auslöst.
Implementierungshinweise
StockSharp hält eine Nettopotsition pro Wertpapier. Die Strategie hält interne Expositionszähler, um die separaten Long- und Short-Tickets des MetaTrader-Experten zu emulieren, und gibt Marktorders mit den akkumulierten Volumina aus, wenn die Basket-Position geschlossen wird.
Da die Logik von Echtzeit-Spreads abhängt, sind Level-1-Daten sowohl in Backtests als auch in Live-Trading-Sitzungen bereitzustellen. Fehlende Bid/Ask-Informationen deaktivieren die Handelsschleife.
Stellen Sie sicher, dass das Handelskonto gleichzeitige Kauf- und Verkaufsorders für dasselbe Instrument unterstützt, da der Algorithmus davon ausgeht, dass beide Seiten der Absicherung bis zur Erfüllung der Ausstiegsbedingung koexistieren können.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Tunnel strategy that uses Bollinger Bands to define a price channel.
/// Buys when price crosses above the lower band (reversal from oversold),
/// sells when price crosses below the upper band (reversal from overbought).
/// </summary>
public class TunnelGen4Strategy : Strategy
{
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbWidth;
private readonly StrategyParam<decimal> _stepPips;
private BollingerBands _bb;
private decimal _prevClose;
private decimal _prevUpper;
private decimal _prevLower;
private decimal _entryPrice;
/// <summary>
/// Bollinger Bands period length.
/// </summary>
public int BbLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
/// <summary>
/// Bollinger Bands width (standard deviations).
/// </summary>
public decimal BbWidth
{
get => _bbWidth.Value;
set => _bbWidth.Value = value;
}
/// <summary>
/// Step distance expressed in pips for profit target.
/// </summary>
public decimal StepPips
{
get => _stepPips.Value;
set => _stepPips.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public TunnelGen4Strategy()
{
_bbLength = Param(nameof(BbLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Indicator");
_bbWidth = Param(nameof(BbWidth), 2.0m)
.SetGreaterThanZero()
.SetDisplay("BB Width", "Bollinger Bands width", "Indicator");
_stepPips = Param(nameof(StepPips), 50m)
.SetGreaterThanZero()
.SetDisplay("Step (pips)", "Distance between tunnel anchors", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bb = null;
_prevClose = 0;
_prevUpper = 0;
_prevLower = 0;
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bb = new BollingerBands
{
Length = BbLength,
Width = BbWidth
};
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.BindEx(_bb, OnProcess);
subscription.Start();
}
private void OnProcess(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
var bb = (BollingerBandsValue)value;
if (bb.UpBand is not decimal upper ||
bb.LowBand is not decimal lower)
return;
if (!_bb.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevUpper = upper;
_prevLower = lower;
return;
}
var close = candle.ClosePrice;
// Buy signal: price crosses above lower band from below
if (_prevClose < _prevLower && close >= lower && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
}
// Sell signal: price crosses below upper band from above
else if (_prevClose > _prevUpper && close <= upper && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
}
// Exit on profit target if in position
if (Position > 0 && _entryPrice > 0)
{
var pipValue = Security?.PriceStep ?? 1m;
var target = _entryPrice + StepPips * pipValue;
if (close >= target)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0 && _entryPrice > 0)
{
var pipValue = Security?.PriceStep ?? 1m;
var target = _entryPrice - StepPips * pipValue;
if (close <= target)
{
BuyMarket();
_entryPrice = 0;
}
}
_prevClose = close;
_prevUpper = upper;
_prevLower = lower;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class tunnel_gen4_strategy(Strategy):
"""Bollinger Band tunnel: buy on cross above lower band, sell on cross below upper."""
def __init__(self):
super(tunnel_gen4_strategy, self).__init__()
self._bb_length = self.Param("BbLength", 20).SetGreaterThanZero().SetDisplay("BB Length", "BB period", "Indicator")
self._bb_width = self.Param("BbWidth", 2.0).SetGreaterThanZero().SetDisplay("BB Width", "BB std devs", "Indicator")
self._step_pips = self.Param("StepPips", 50.0).SetGreaterThanZero().SetDisplay("Step Pips", "Profit target distance", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(tunnel_gen4_strategy, self).OnReseted()
self._prev_close = 0
self._prev_upper = 0
self._prev_lower = 0
self._entry_price = 0
def OnStarted2(self, time):
super(tunnel_gen4_strategy, self).OnStarted2(time)
self._prev_close = 0
self._prev_upper = 0
self._prev_lower = 0
self._entry_price = 0
self._bb = BollingerBands()
self._bb.Length = self._bb_length.Value
self._bb.Width = self._bb_width.Value
sub = self.SubscribeCandles(self.CandleType)
sub.BindEx(self._bb, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, self._bb)
self.DrawOwnTrades(area)
def OnProcess(self, candle, bb_val):
if candle.State != CandleStates.Finished:
return
if not self._bb.IsFormed:
return
upper = None
lower = None
for inner in bb_val.InnerValues:
name = str(inner.Key.Name) if hasattr(inner.Key, 'Name') else str(inner.Key)
if "Up" in name or "up" in name:
upper = float(inner.Value) if not inner.Value.IsEmpty else None
elif "Low" in name or "low" in name or "Down" in name or "down" in name:
lower = float(inner.Value) if not inner.Value.IsEmpty else None
if upper is None or lower is None:
return
close = float(candle.ClosePrice)
if self._prev_close == 0:
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
return
# Buy: price crosses above lower band from below
if self._prev_close < self._prev_lower and close >= lower and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
# Sell: price crosses below upper band from above
elif self._prev_close > self._prev_upper and close <= upper and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
# Exit on profit target
step = self._step_pips.Value
if self.Position > 0 and self._entry_price > 0:
if close >= self._entry_price + step:
self.SellMarket()
self._entry_price = 0
elif self.Position < 0 and self._entry_price > 0:
if close <= self._entry_price - step:
self.BuyMarket()
self._entry_price = 0
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
def CreateClone(self):
return tunnel_gen4_strategy()