Treppenstrategie
Die Stairs-Strategie reproduziert das Verhalten des ursprünglichen MetaTrader-Experten. Es beginnt mit der Platzierung symmetrischer Stop-Orders rund um den aktuellen Briefkurs und baut dann das Raster kontinuierlich um die letzte Ausführung herum neu auf. Gewinne werden in Preisschritten (Pips) ohne Gewichtung nach Volumen akkumuliert, genau wie im Quellskript. Wenn ein Gewinnziel erreicht wird, liquidiert die Strategie alle Positionen gemäß der Marktorder, entfernt alle ausstehenden Stopps und setzt das Raster zurück.
Handelslogik
- Wenn keine Positionen offen sind, platzieren Sie einen Kaufstopp und einen Verkaufsstopp in einem Abstand von
ChannelSteps / 2 Preisschritten über und unter dem aktuellen Briefkurs.
- Nachdem die erste Stop-Order ausgeführt wurde, stellen Sie das Raster wieder auf den ausgeführten Preis ein:
- Wenn weniger als zwei aktive Stop-Orders vorhanden sind, stornieren Sie die veralteten.
- Solange der aktuelle Gebotspreis innerhalb der Hälfte der Kanalentfernung vom letzten Eintrag bleibt, platzieren Sie einen neuen Kaufstopp und Verkaufsstopp
ChannelSteps entfernt von der letzten Füllung.
- Wenn
AddLots aktiviert ist, erhöhen Sie nach jeder Ausführung das Volumen der ausstehenden Aufträge um das Basislos.
- Führen Sie zwei laufende Listen mit allen Long- und Short-Einträgen, um den von der MT4-Version verwendeten Hedged Basket zu reproduzieren.
- Berechnen Sie den nicht realisierten Gewinn des Korbs für jede fertige Kerze unter Verwendung des besten Gebots für Long-Positionen und des besten Briefs für Short-Positionen. Entfernungen werden durch die Preisstufe des Instruments normalisiert und spiegeln die ursprüngliche Punktberechnung wider.
- Lösen Sie eine vollständige Liquidation aus, wenn einer der Schwellenwerte überschritten wird:
ProfitSteps – Gewinn, der nur durch das aktuelle Symbol erzeugt wird.
CommonProfitSteps – Gewinn im gesamten Warenkorb.
- Die Liquidation sendet Marktaufträge, um jedes Long- und Short-Engagement separat zu schließen. Ausstehende Stop-Orders werden storniert, sobald der Korb flach ist.
Hinweis: Der ursprüngliche Experte hat bei der Registrierung ausstehender Aufträge Stop-Loss-Levels angegeben. StockSharp unterstützt keine pro-Order-Schutzebenen durch die übergeordnete Ebene API, daher schließt der Port Geschäfte ausschließlich über die oben beschriebene gewinnbasierte Logik ab.
Parameter
| Parameter |
Beschreibung |
Standard |
ChannelSteps |
Abstand (in Mindestpreisschritten) zwischen den symmetrischen Stop-Orders. |
1000 |
ProfitSteps |
Gewinnschwelle (in Schritten), die zum Schließen des lokalen Warenkorbs erforderlich ist. |
1500 |
CommonProfitSteps |
Globale Gewinnschwelle (in Schritten), die eine vollständige Liquidation erzwingt. |
1000 |
AddLots |
Wenn diese Option aktiviert ist, erhöhen Sie nach jeder Ausführung das nächste ausstehende Auftragsvolumen um das Basislos. |
true |
BaseVolume |
Das für das allererste Stop-Order-Paar verwendete Volumen. |
0.1m |
CandleType |
Zeitrahmen für Kerzenabonnements und Handelsmanagement. |
1 minute |
Hinweise zur Implementierung
- Verwendet die StockSharp-Hochebene API mit
SubscribeCandles() und Bind(), um nur fertige Kerzen zu verarbeiten.
- Verfolgt einzelne Einträge innerhalb von
OnOwnTradeReceived, sodass die Gewinnberechnung die Absicherungslogik der MQL-Version nachahmen kann.
- Gewinnschwellen basieren auf reinen Preisschrittabständen, ohne Multiplikation mit dem ausgeführten Volumen, und entsprechen der Art und Weise, wie der MT4-Experte die Pips summiert hat.
- Alle Stop-Orders werden über
BuyStop und SellStop erstellt, während Exits mit Market-Orders ausgeführt werden, um die Logik über Datenanbieter hinweg portierbar zu halten.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Stairs grid strategy: places trades at regular ATR-based intervals,
/// adding to position on trending moves, closing on profit target.
/// </summary>
public class StairsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _gridMultiplier;
private readonly StrategyParam<int> _maxLayers;
private readonly StrategyParam<decimal> _profitMultiplier;
private readonly StrategyParam<int> _emaLength;
private decimal _entryPrice;
private decimal _lastGridPrice;
private int _gridCount;
private decimal _prevEma;
public StairsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for grid step.", "Indicators");
_gridMultiplier = Param(nameof(GridMultiplier), 1.5m)
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step.", "Grid");
_maxLayers = Param(nameof(MaxLayers), 5)
.SetDisplay("Max Layers", "Maximum grid layers.", "Grid");
_profitMultiplier = Param(nameof(ProfitMultiplier), 2.0m)
.SetDisplay("Profit Multiplier", "ATR multiplier for profit target.", "Grid");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "EMA for trend direction.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal GridMultiplier
{
get => _gridMultiplier.Value;
set => _gridMultiplier.Value = value;
}
public int MaxLayers
{
get => _maxLayers.Value;
set => _maxLayers.Value = value;
}
public decimal ProfitMultiplier
{
get => _profitMultiplier.Value;
set => _profitMultiplier.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_lastGridPrice = 0;
_gridCount = 0;
_prevEma = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0 || _prevEma == 0)
{
_prevEma = emaVal;
return;
}
var close = candle.ClosePrice;
var gridStep = atrVal * GridMultiplier;
var profitTarget = atrVal * ProfitMultiplier;
// Check profit target
if (Position > 0 && _entryPrice > 0)
{
if (close - _entryPrice >= profitTarget || close < emaVal)
{
SellMarket();
_gridCount = 0;
_entryPrice = 0;
_lastGridPrice = 0;
}
}
else if (Position < 0 && _entryPrice > 0)
{
if (_entryPrice - close >= profitTarget || close > emaVal)
{
BuyMarket();
_gridCount = 0;
_entryPrice = 0;
_lastGridPrice = 0;
}
}
// Grid: add to winning direction
if (Position > 0 && _lastGridPrice > 0 && _gridCount < MaxLayers)
{
if (close - _lastGridPrice >= gridStep)
{
BuyMarket();
_lastGridPrice = close;
_gridCount++;
}
}
else if (Position < 0 && _lastGridPrice > 0 && _gridCount < MaxLayers)
{
if (_lastGridPrice - close >= gridStep)
{
SellMarket();
_lastGridPrice = close;
_gridCount++;
}
}
// Initial entry based on trend
if (Position == 0)
{
var emaRising = emaVal > _prevEma;
var emaFalling = emaVal < _prevEma;
if (emaRising && close > emaVal)
{
_entryPrice = close;
_lastGridPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (emaFalling && close < emaVal)
{
_entryPrice = close;
_lastGridPrice = close;
_gridCount = 0;
SellMarket();
}
}
_prevEma = emaVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
class stairs_strategy(Strategy):
def __init__(self):
super(stairs_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for grid step", "Indicators")
self._grid_multiplier = self.Param("GridMultiplier", 1.5) \
.SetDisplay("Grid Multiplier", "ATR multiplier for grid step", "Grid")
self._max_layers = self.Param("MaxLayers", 5) \
.SetDisplay("Max Layers", "Maximum grid layers", "Grid")
self._profit_multiplier = self.Param("ProfitMultiplier", 2.0) \
.SetDisplay("Profit Multiplier", "ATR multiplier for profit target", "Grid")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA for trend direction", "Indicators")
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def GridMultiplier(self):
return self._grid_multiplier.Value
@property
def MaxLayers(self):
return self._max_layers.Value
@property
def ProfitMultiplier(self):
return self._profit_multiplier.Value
@property
def EmaLength(self):
return self._ema_length.Value
def OnStarted2(self, time):
super(stairs_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val, ema_val):
if candle.State != CandleStates.Finished:
return
atr_v = float(atr_val)
ema_v = float(ema_val)
if atr_v <= 0 or self._prev_ema == 0:
self._prev_ema = ema_v
return
close = float(candle.ClosePrice)
grid_step = atr_v * float(self.GridMultiplier)
profit_target = atr_v * float(self.ProfitMultiplier)
# Check profit target
if self.Position > 0 and self._entry_price > 0:
if close - self._entry_price >= profit_target or close < ema_v:
self.SellMarket()
self._grid_count = 0
self._entry_price = 0.0
self._last_grid_price = 0.0
elif self.Position < 0 and self._entry_price > 0:
if self._entry_price - close >= profit_target or close > ema_v:
self.BuyMarket()
self._grid_count = 0
self._entry_price = 0.0
self._last_grid_price = 0.0
# Grid: add to winning direction
max_layers = self.MaxLayers
if self.Position > 0 and self._last_grid_price > 0 and self._grid_count < max_layers:
if close - self._last_grid_price >= grid_step:
self.BuyMarket()
self._last_grid_price = close
self._grid_count += 1
elif self.Position < 0 and self._last_grid_price > 0 and self._grid_count < max_layers:
if self._last_grid_price - close >= grid_step:
self.SellMarket()
self._last_grid_price = close
self._grid_count += 1
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema = ema_v
return
# Initial entry based on trend
if self.Position == 0:
ema_rising = ema_v > self._prev_ema
ema_falling = ema_v < self._prev_ema
if ema_rising and close > ema_v:
self._entry_price = close
self._last_grid_price = close
self._grid_count = 0
self.BuyMarket()
elif ema_falling and close < ema_v:
self._entry_price = close
self._last_grid_price = close
self._grid_count = 0
self.SellMarket()
self._prev_ema = ema_v
def OnReseted(self):
super(stairs_strategy, self).OnReseted()
self._entry_price = 0.0
self._last_grid_price = 0.0
self._grid_count = 0
self._prev_ema = 0.0
def CreateClone(self):
return stairs_strategy()