Auf GitHub ansehen

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

  1. Ü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.
  2. 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.
  3. 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.
  4. 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.
  5. 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;
	}
}