MartingaleEA-5-Level-Strategie (StockSharp)
Die MartingaleEA-5 Levels-Strategie ist eine direkte Portierung des MetaTrader 5 Expertenberaters „MartingaleEA-5 Levels“ auf das StockSharp High-Level API. Das System überwacht eine bestehende Position und erstellt ein fünfstufiges Durchschnittsraster, wann immer sich der Markt dagegen bewegt. Die gesamte Logik läuft auf fertigen Kerzen ab, wodurch das Verhalten sowohl in historischen Tests als auch im Live-Handel reproduzierbar bleibt.
Handelslogik
- Überwachung des bestehenden Engagements – Die Strategie geht davon aus, dass eine anfängliche Long- oder Short-Position vorhanden ist. Sie können den ersten Handel manuell oder über eine andere Strategie eröffnen.
- Erkennung unerwünschter Bewegungen – Bei jeder abgeschlossenen Kerze misst die Strategie, wie weit sich der aktuelle Preis vom Eintrag mit dem schlechtesten Preis der aktiven Gruppe (höchster Long oder niedrigster Short) entfernt hat.
- Martingale Ergänzungen – wenn der gleitende Verlust der Gruppe negativ ist und die Gegenbewegung die konfigurierten kumulativen Distanzen überschreitet, sendet die Strategie zusätzliche Marktaufträge. Jede weitere Bestellung multipliziert die vorherige mit
VolumeMultiplier. Es können bis zu fünf Stufen konfiguriert werden; Der Parameter MaxAdditions begrenzt, wie viele davon tatsächlich verwendet werden.
- Gewinn- und Verlustzielsetzung – während eine Gruppe geöffnet ist, summiert die Strategie kontinuierlich den nicht realisierten Gewinn- und Verlustgewinn für diese Richtung. Sobald die Gesamtsumme
TakeProfitCurrency erreicht oder unter StopLossCurrency fällt, werden alle Orders auf dieser Seite mit einer Marktorder geschlossen und die Martingalzähler werden zurückgesetzt.
- Volumennormalisierung – jedes Auftragsvolumen durchläuft die
VolumeStep, MinVolume und MaxVolume des Instruments, um das Senden nicht ausführbarer Mengen zu vermeiden.
Parameter
| Name |
Beschreibung |
Standard |
EnableMartingale |
Schaltet die Mittelungs- und Liquidationslogik ein oder aus. |
true |
VolumeMultiplier |
Faktor, der beim Hinzufügen einer neuen Ebene auf das vorherige Auftragsvolumen angewendet wird. |
2.0 |
MaxAdditions |
Maximale Anzahl Martingalschritte pro Richtung (bis zu fünf). |
4 |
Level1DistancePips |
Anfängliche negative Distanz (in Pips) vor der Eröffnung der zweiten Order. |
300 |
Level2DistancePips |
Für die dritte Ordnung ist zusätzlicher Abstand erforderlich. |
400 |
Level3DistancePips |
Für die vierte Ordnung ist zusätzlicher Abstand erforderlich. |
500 |
Level4DistancePips |
Für die fünfte Ordnung ist zusätzlicher Abstand erforderlich. |
600 |
Level5DistancePips |
Zusätzlicher Abstand für die sechste Ordnung erforderlich (falls zulässig). |
700 |
TakeProfitCurrency |
Nicht realisierter Gewinn (Kontowährung), der die gesamte Gruppe schließt. |
200 |
StopLossCurrency |
Nicht realisierter Verlust (Kontowährung), der einen Notausgang erzwingt. |
-500 |
CandleType |
Für Auswertungen verwendeter Zeitrahmen (Standard-1-Minuten-Kerzen). |
TimeFrame(1m) |
Pip-Umrechnung – jede Distanz wird mit dem Preisschritt des Instruments (PriceStep oder MinPriceStep) multipliziert. Für Symbole, die in gebrochenen Pips angegeben werden, passen Sie die Werte entsprechend an.
Hinweise und Empfehlungen
- Die Implementierung spiegelt die ursprüngliche EA wider, einschließlich der Annahme, dass jeweils nur ein Richtungskorb aktiv ist. Das gleichzeitige Öffnen von Positionen in beide Richtungen führt dazu, dass jede Seite unabhängig verwaltet wird.
- Da die Strategie nur auf Kerzenschließungen reagiert, wählen Sie einen Zeitrahmen, der der gewünschten Reaktionsfähigkeit entspricht. Kürzere Zeitrahmen ahmen das Verhalten auf Tick-Ebene besser nach.
- Martingale-Techniken erhöhen das Risiko. Testen Sie immer mit realistischen Slippage- und Provisionsmodellen und definieren Sie konservative Stop-Levels, bevor Sie die Strategie auf Live-Märkten aktivieren.
- Die Strategie erstellt noch keinen Python-Port. Wie gewünscht ist nur die C#-High-Level-Implementierung enthalten.
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>
/// Martingale averaging strategy converted from "MartingaleEA-5 Levels".
/// Opens initial position on simple momentum, then averages down with
/// increasing lot sizes up to 5 levels. Closes when floating profit
/// reaches target or stop threshold.
/// </summary>
public class MartingaleEa5LevelsStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _maxAdditions;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private SimpleMovingAverage _sma;
private decimal? _prevClose;
private decimal? _prevMa;
private readonly List<(decimal price, decimal vol)> _entries = new();
private int _additions;
private decimal _lastVolume;
private Sides? _activeSide;
private int _candleCount;
private int _lastOrderCandle;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
public int MaxAdditions
{
get => _maxAdditions.Value;
set => _maxAdditions.Value = value;
}
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
public MartingaleEa5LevelsStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "SMA period for entry signal", "Indicators");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Volume Multiplier", "Multiplier for each martingale level", "Money Management");
_maxAdditions = Param(nameof(MaxAdditions), 4)
.SetDisplay("Max Additions", "Maximum martingale additions", "Money Management");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Floating profit % to close group", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Floating loss % to close group", "Risk");
}
protected override void OnReseted()
{
base.OnReseted();
_sma = default;
_prevClose = null;
_prevMa = null;
_entries.Clear();
_additions = 0;
_lastVolume = 0;
_activeSide = null;
_candleCount = 0;
_lastOrderCandle = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_sma = new SimpleMovingAverage { Length = MaPeriod };
_prevClose = null;
_prevMa = null;
_entries.Clear();
_additions = 0;
_lastVolume = 0;
_activeSide = null;
_candleCount = 0;
_lastOrderCandle = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_sma.IsFormed)
{
_prevClose = candle.ClosePrice;
_prevMa = smaValue;
return;
}
_candleCount++;
var close = candle.ClosePrice;
var volume = Volume;
if (volume <= 0)
volume = 1;
// Cooldown: allow at most one order per 100 candles
var cooldownPassed = (_candleCount - _lastOrderCandle) >= 100;
// Check martingale closure first
if (_entries.Count > 0)
{
var floatingPnl = CalculateFloatingPnl(close);
var totalCost = CalculateTotalCost();
if (totalCost > 0)
{
var pnlPercent = floatingPnl / totalCost * 100m;
if (cooldownPassed && (pnlPercent >= TakeProfitPercent || pnlPercent <= -StopLossPercent))
{
// Close entire position
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(Math.Abs(Position));
_lastOrderCandle = _candleCount;
_entries.Clear();
_additions = 0;
_lastVolume = 0;
_activeSide = null;
_prevClose = close;
_prevMa = smaValue;
return;
}
}
// Check for martingale additions
if (cooldownPassed && _additions < MaxAdditions)
{
var avgPrice = CalculateAvgPrice();
var adversePercent = _activeSide == Sides.Buy
? (avgPrice - close) / avgPrice * 100m
: (close - avgPrice) / avgPrice * 100m;
// Add at each 0.3% adverse move beyond previous level
var threshold = 0.3m * (_additions + 1);
if (adversePercent >= threshold)
{
var nextVol = _lastVolume * VolumeMultiplier;
if (nextVol < 1) nextVol = 1;
if (_activeSide == Sides.Buy)
{
BuyMarket(nextVol);
_entries.Add((close, nextVol));
}
else
{
SellMarket(nextVol);
_entries.Add((close, nextVol));
}
_lastVolume = nextVol;
_additions++;
_lastOrderCandle = _candleCount;
}
}
}
// Initial entry signal: MA crossover
if (cooldownPassed && _prevClose != null && _prevMa != null && _activeSide == null)
{
var buySignal = _prevClose.Value < _prevMa.Value && close > smaValue;
var sellSignal = _prevClose.Value > _prevMa.Value && close < smaValue;
if (buySignal)
{
BuyMarket(volume);
_entries.Clear();
_entries.Add((close, volume));
_additions = 0;
_lastVolume = volume;
_activeSide = Sides.Buy;
_lastOrderCandle = _candleCount;
}
else if (sellSignal)
{
SellMarket(volume);
_entries.Clear();
_entries.Add((close, volume));
_additions = 0;
_lastVolume = volume;
_activeSide = Sides.Sell;
_lastOrderCandle = _candleCount;
}
}
_prevClose = close;
_prevMa = smaValue;
}
private decimal CalculateFloatingPnl(decimal currentPrice)
{
var pnl = 0m;
foreach (var (price, vol) in _entries)
{
if (_activeSide == Sides.Buy)
pnl += (currentPrice - price) * vol;
else
pnl += (price - currentPrice) * vol;
}
return pnl;
}
private decimal CalculateTotalCost()
{
var cost = 0m;
foreach (var (price, vol) in _entries)
cost += price * vol;
return cost;
}
private decimal CalculateAvgPrice()
{
var totalVol = 0m;
var totalCost = 0m;
foreach (var (price, vol) in _entries)
{
totalVol += vol;
totalCost += price * vol;
}
return totalVol > 0 ? totalCost / totalVol : 0;
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class martingale_ea_5_levels_strategy(Strategy):
"""
Martingale EA 5 Levels: SMA crossover entry (simplified, no martingale averaging in Python).
"""
def __init__(self):
super(martingale_ea_5_levels_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "SMA period", "Indicators")
self._take_profit_pct = self.Param("TakeProfitPercent", 0.5).SetDisplay("TP %", "Take profit percent", "Risk")
self._stop_loss_pct = self.Param("StopLossPercent", 2.0).SetDisplay("SL %", "Stop loss percent", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
self._prev_close = None
self._prev_ma = None
self._entry_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(martingale_ea_5_levels_strategy, self).OnReseted()
self._prev_close = None
self._prev_ma = None
self._entry_price = 0.0
def OnStarted2(self, time):
super(martingale_ea_5_levels_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ma = float(sma_val)
tp_pct = float(self._take_profit_pct.Value) / 100.0
sl_pct = float(self._stop_loss_pct.Value) / 100.0
if self.Position > 0 and self._entry_price > 0:
if close >= self._entry_price * (1.0 + tp_pct) or close <= self._entry_price * (1.0 - sl_pct):
self.SellMarket()
self._entry_price = 0.0
self._prev_close = close
self._prev_ma = ma
return
elif self.Position < 0 and self._entry_price > 0:
if close <= self._entry_price * (1.0 - tp_pct) or close >= self._entry_price * (1.0 + sl_pct):
self.BuyMarket()
self._entry_price = 0.0
self._prev_close = close
self._prev_ma = ma
return
if self._prev_close is not None and self._prev_ma is not None and self.Position == 0:
if self._prev_close < self._prev_ma and close > ma:
self.BuyMarket()
self._entry_price = close
elif self._prev_close > self._prev_ma and close < ma:
self.SellMarket()
self._entry_price = close
self._prev_close = close
self._prev_ma = ma
def CreateClone(self):
return martingale_ea_5_levels_strategy()