Die SwingTrader-Strategie ist eine StockSharp-Portierung des MetaTrader 4-Expertenberaters SwingTrader.mq4. Das Original EA sucht
Bollinger Bandumkehrungen: Wenn der Preis vom äußeren Band abprallt und der nächste Balken die Mittellinie kreuzt, öffnet der Berater eine
Position und beginnt mit dem Aufbau eines Mittelungsgitters im Martingal-Stil. Die übersetzte Strategie reproduziert das gleiche Verhalten auf hoher Ebene
unter Verwendung von StockSharp-Kerzen, Bollinger-Bändern aus StockSharp.Algo.Indicators und den Ordnungshelfern des Frameworks (BuyMarket,
SellMarket). Die Volumenskalierung, die Breite des Rasters und die Liquidationsregeln spiegeln den MT4-Code unter Berücksichtigung der Börse wider
Grenzwerte, die durch die Security-Metadaten bereitgestellt werden.
Handelslogik
Abonnieren Sie den konfigurierten Zeitrahmen (CandleType) und füttern Sie einen Bollinger-Bandindikator mit einer Länge von BollingerPeriod und einem
fester Standardabweichungsmultiplikator von 2.
Arbeiten Sie nur mit fertigen Kerzen; Der Indikator-Callback ignoriert teilweise geformte Balken, um den MT4 IsNewCandle() zu reproduzieren.
Wache.
Verfolgen Sie, ob die vorherige Kerze das obere oder untere Band berührt hat. Das boolesche Paar _upTouch / _downTouch folgt dem
Original-Umschaltlogik, die nur eine Seite aktiv hält, bis das gegenüberliegende Band berührt wird.
Wenn kein Warenkorb geöffnet ist:
Eröffnen Sie eine Long-Position, wenn der letzte abgeschlossene Balken das mittlere Band überschreitet, nachdem er zuvor das untere Band berührt hat.
Eröffnen Sie eine Short-Position, wenn der Balken das mittlere Band unterschritten hat, nachdem er das obere Band berührt hat.
Das Volumen erster Ordnung entspricht InitialVolume (nach Börsenrundung) und die anfängliche Gitterbreite entspricht der letzten Entfernung
zwischen dem oberen und unteren Bollinger-Band.
Wenn ein Korb vorhanden ist, achten Sie ab der ersten Füllung auf eine nachteilige Bewegung um eine volle Bandbreite:
Wenn bei Long-Positionen das Tief der Kerze mindestens eine Bandbreite unter dem Ankerpreis liegt, kaufen Sie ein weiteres Segment, dessen Größe vervielfacht wird
um Multiplier mit jedem neuen Level;
Wenn bei Short-Positionen das Hoch der Kerze eine Bandbreite über dem Ankerpreis liegt, verkaufen Sie ein zusätzliches Stück davon
Multiplikatorlogik.
Sammeln Sie so lange neue Aufträge, bis entweder das Gewinn- oder das maximal tolerierte Verlustziel erreicht ist.
Geldmanagement und Exits
Der Helfer CalculateUnrealizedProfit reproduziert die MT4-Floating-PnL-Berechnung, indem er Preisunterschiede in Preise umwandelt
Schritte (Security.PriceStep) und Schrittwert (Security.StepPrice).
Der Proxy für das investierte Kapital verwendet die ursprüngliche Formel Lots * Price / TickSize * TickValue / 30, wobei Lots die Summe ist
der Gittervolumina und die Tick-Parameter stammen aus Security.
Schließen Sie den gesamten Warenkorb, sobald der variable Gewinn TakeProfitFactor * invested capital übersteigt.
Erzwingen Sie eine Notfallliquidation, wenn der schwebende Verlust 10 * TakeProfitFactor * invested capital erreicht (gleiches Verhältnis wie
MT4-Code).
Alle Exits werden mit Marktaufträgen in die entgegengesetzte Richtung ausgeführt; Sobald es flach ist, wird der Gitterzustand zurückgesetzt und es müssen neue Berührungen vorgenommen werden
erkannt, bevor ein weiterer Eintrag ausgelöst werden kann.
Parameter
Name
Typ
Standard
Beschreibung
TakeProfitFactor
decimal
0.05
Multiplikator, der auf das investierte Kapital angewendet wird, um das Gewinnziel zu definieren.
Multiplier
decimal
1.5
Volumenmultiplikator für jeden weiteren Mittelungsauftrag.
BollingerPeriod
int
20
Anzahl der Kerzen, die vom Bollinger Bands-Indikator verwendet werden.
InitialVolume
decimal
1
Basisvolumen des ersten Handels in einem neuen Korb (gerundet auf die Handelsplatzgrenzen).
CandleType
DataType
15-minütiger Zeitrahmen
Zeitrahmen, der für die Signalgenerierung verwendet wird.
Unterschiede zum Original EA
StockSharp funktioniert mit Nettopositionen; Die Strategie verwaltet explizite Listen von Rastereinträgen, um die Ticket-basierte Reihenfolge von MT4 zu emulieren
Handhabung.
Stattdessen werden Exchange-Volumenfilter (Security.MinVolume, Security.VolumeStep, Security.MaxVolume) automatisch angewendet
des manuellen Aufrufs von CheckVolumeValue.
Signale werden bei geschlossenen Kerzen ausgewertet; Intrabar-Trigger aus der MT4-Version werden durch die Verwendung von Kerzenhochs und -tiefs angenähert
für Mittelungsentscheidungen.
Aufträge werden immer als Marktanweisungen gesendet, während MT4 OrderSend mit expliziten Bid/Ask-Parametern verwendet.
Nutzungshinweise
Stellen Sie realistische Metadaten für das gehandelte Instrument bereit: PriceStep, StepPrice, MinVolume, VolumeStep und MaxVolume müssen
für die Gewinn-, Verlust- und Volumenberechnungen ausgefüllt werden, damit sie dem MT4-Verhalten entsprechen.
Da das Durchschnittsraster geometrisch skaliert wird, testen Sie die Konfiguration anhand historischer Daten und berücksichtigen Sie die Broker-Marge
Informieren Sie sich über die Anforderungen, bevor Sie es live ausführen.
Die Gitterbreite entspricht der aktuellen Bollinger-Bandbreite; Die Änderung von BollingerPeriod wirkt sich direkt auf den Eintrittszeitpunkt und das Raster aus
Abstand. Validieren Sie die Empfindlichkeit während der Optimierung.
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Simplified from "SwingTrader" MetaTrader expert.
/// Uses Bollinger Band touches to detect swing direction, then enters on middle-band cross.
/// </summary>
public class SwingTraderStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private BollingerBands _bollinger;
private bool _upTouch;
private bool _downTouch;
private decimal? _prevClose;
private decimal? _prevMiddle;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
public SwingTraderStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signals", "General");
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 2m)
.SetGreaterThanZero()
.SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };
_upTouch = false;
_downTouch = false;
_prevClose = null;
_prevMiddle = null;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFinal)
return;
if (bbValue is not BollingerBandsValue bbVal)
return;
if (bbVal.UpBand is not decimal upper || bbVal.LowBand is not decimal lower || bbVal.MovingAverage is not decimal middle)
return;
if (!_bollinger.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevMiddle = middle;
return;
}
var close = candle.ClosePrice;
// Track Bollinger touches
if (candle.HighPrice > upper)
{
_upTouch = true;
_downTouch = false;
}
if (candle.LowPrice < lower)
{
_downTouch = true;
_upTouch = false;
}
if (_prevClose is null || _prevMiddle is null)
{
_prevClose = close;
_prevMiddle = middle;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Buy: had a lower band touch, now price crosses above middle
var buySignal = _downTouch && _prevClose.Value < _prevMiddle.Value && close > middle;
// Sell: had an upper band touch, now price crosses below middle
var sellSignal = _upTouch && _prevClose.Value > _prevMiddle.Value && close < middle;
if (buySignal)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
if (Position <= 0)
BuyMarket(volume);
_downTouch = false;
}
else if (sellSignal)
{
if (Position > 0)
SellMarket(Position);
if (Position >= 0)
SellMarket(volume);
_upTouch = false;
}
_prevClose = close;
_prevMiddle = middle;
}
/// <inheritdoc />
protected override void OnReseted()
{
_bollinger = null;
_upTouch = false;
_downTouch = false;
_prevClose = null;
_prevMiddle = null;
base.OnReseted();
}
}
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
class swing_trader_strategy(Strategy):
"""Bollinger Band touch swing: buy on lower touch + middle cross up, sell on upper touch + middle cross down."""
def __init__(self):
super(swing_trader_strategy, self).__init__()
self._bb_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("BB Period", "Bollinger Bands period", "Indicators")
self._bb_width = self.Param("BollingerWidth", 2.0).SetGreaterThanZero().SetDisplay("BB Width", "Bollinger Bands deviation", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe for signals", "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(swing_trader_strategy, self).OnReseted()
self._up_touch = False
self._down_touch = False
self._prev_close = 0
self._prev_middle = 0
def OnStarted2(self, time):
super(swing_trader_strategy, self).OnStarted2(time)
self._up_touch = False
self._down_touch = False
self._prev_close = 0
self._prev_middle = 0
self._bb = BollingerBands()
self._bb.Length = self._bb_period.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 = float(bb_val.UpBand)
lower = float(bb_val.LowBand)
middle = float(bb_val.MovingAverage)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# Track Bollinger touches
if high > upper:
self._up_touch = True
self._down_touch = False
if low < lower:
self._down_touch = True
self._up_touch = False
if self._prev_close == 0 or self._prev_middle == 0:
self._prev_close = close
self._prev_middle = middle
return
# Buy: had lower band touch, now price crosses above middle
buy_signal = self._down_touch and self._prev_close < self._prev_middle and close > middle
# Sell: had upper band touch, now price crosses below middle
sell_signal = self._up_touch and self._prev_close > self._prev_middle and close < middle
if buy_signal:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
self._down_touch = False
elif sell_signal:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
self._up_touch = False
self._prev_close = close
self._prev_middle = middle
def CreateClone(self):
return swing_trader_strategy()