Auf GitHub ansehen

Martingale Breakout-Strategie

Überblick

Die Martingale Breakout Strategy ist eine StockSharp-Portierung des MetaTrader-Expertenberaters MartinGaleBreakout.mq5. Das System Wartet auf ungewöhnlich große Ausbruchskerzen und platziert eine einzelne Marktorder in Ausbruchsrichtung. Während das Original EA Verfolgt eine „magische Zahl“, um seine Positionen zu verwalten. Die StockSharp-Implementierung basiert auf dem Strategiekontext, also dem Verhalten ist praktisch derselbe, wenn die Strategie isoliert ausgeführt wird.

Der Algorithmus konzentriert sich auf zwei Kernideen:

  1. Breakout-Erkennung – die Strategie untersucht die Größe jeder fertigen Kerze und vergleicht sie mit der durchschnittlichen Spanne der Kerze vorherigen zehn Kerzen. Wenn die aktuelle Spanne dreimal größer als der Durchschnitt ist und die Kerze stark schließt Je nach Richtung des Ausbruchs wird ein Handelssignal erzeugt.
  2. Erholung im Martingale-Stil – die Strategie verfolgt schwankende Gewinne und Verluste. Immer wenn der nicht realisierte PnL den erreicht Bei konfigurierter Verlustschwelle werden sofort alle offenen Positionen geschlossen und das nächste Gewinnziel, also der folgende Trade, erhöht versucht, den Schaden auszugleichen. Sobald das erhöhte Ziel erreicht ist, werden die Schwellenwerte auf die ursprünglichen Werte zurückgesetzt.

Der Port behält alle Geldverwaltungsparameter aus dem MQL5-Code bei, einschließlich des für die Marge reservierten Saldoprozentsatzes prozentuale Gewinn- und Verlustziele und der Multiplikator, der die Take-Profit-Distanz während der Erholungsphase erweitert.

Handelslogik

  1. Abonnieren Sie die konfigurierte Kerzenserie und warten Sie auf fertige Kerzen.
  2. Berechnen Sie den Kerzenbereich (High - Low) und pflegen Sie einen Puffer fester Größe mit den vorherigen zehn Bereichen, um den zu bestimmen Referenzdurchschnitt, der zur Ausbruchserkennung verwendet wird.
  3. Berechnen Sie den variablen PnL, indem Sie die durchschnittlichen Einstiegspreise für die Long- und Short-Seiten verfolgen. Wenn der nicht realisierte PnL den überschreitet Wenn das Gewinnziel erreicht ist oder die Stop-Loss-Schwelle überschritten wird, schließen Sie sofort alle Positionen und setzen Sie den Wiederherstellungsstatus wie in der Abbildung zurück ursprünglicher Fachberater.
  4. Überspringen Sie die Auftragserteilung, während die Strategie bereits eine Position hält oder wenn der Verbindungsstatus den Handel nicht zulässt.
  5. Wenn eine zinsbullische Ausbruchskerze erscheint, legen Sie die Größe der Order so fest, dass der erwartete Gewinn dem aktuellen Ziel entspricht. Der Take-Profit Die Entfernung in Preisschritten wird während der Wiederherstellung multipliziert, genau wie der Parameter TP_Points_Multiplier aus dem Parameter EA.
  6. Validieren Sie das berechnete Volumen anhand der Gerätegrenzen (Minimum, Maximum und Schritt) und stellen Sie sicher, dass die erforderliche Marge eingehalten wird die konfigurierte Guthabenzuteilung oder die verfügbaren freien Mittel nicht überschreitet. Wenn die Einschränkungen eingehalten werden, reichen Sie eine ein Marktkaufauftrag.
  7. Wiederholen Sie den gleichen Vorgang für rückläufige Ausbrüche und erteilen Sie stattdessen einen Marktverkaufsauftrag.

Die Kombination dieser Regeln stellt das Verhalten des ursprünglichen MetaTrader-Systems wieder her, einschließlich des Übergangs hinein und heraus des Erholungsmodus nach einem Stop-Loss-Ereignis.

Parameter

Parameter Beschreibung Standard
TakeProfitPoints Abstand zwischen Einstiegspreis und Take-Profit-Preis, ausgedrückt in Preisschritten. 50
BalancePercentAvailable Maximaler Prozentsatz des Kontostands, der für die Marge bei einem einzelnen Trade reserviert werden kann. 50
TakeProfitPercentOfBalance Zielgewinn ausgedrückt als Prozentsatz des aktuellen Saldos. 0.1
StopLossPercentOfBalance Stop-Loss-Größe, ausgedrückt als Prozentsatz des aktuellen Saldos. 10
RecoveryStartFraction Bruchteil des Stop-Loss, der vor dem Wechsel in den Wiederherstellungsmodus verwendet wurde. 0.1
RecoveryPointsMultiplier Multiplikator, der während der Erholung auf die Take-Profit-Distanz angewendet wird. 1
CandleType Von der Strategie verwendete Kerzendatenquelle (Zeitrahmen, Tick-Kerzen usw.). 15-minute time frame

Zusätzliche Hinweise

  • Die Volumenberechnung repliziert den MetaTrader-Helfer CalcLotWithTP. Daraus wird die Losgröße abgeleitet, die erforderlich ist, um den aktuellen Wert zu erreichen Gewinnziel für eine bestimmte Preisbewegung und normalisiert das Ergebnis dann auf den Volumenschritt des Instruments.
  • Margin-Prüfungen werden nach dem gleichen Prinzip wie CheckVolumeValue und dem in MQL verwendeten Balance-Prozentsatz-Filter durchgeführt. Version. Aufträge werden abgelehnt, wenn die erforderliche Marge den zulässigen Anteil des Guthabens oder die von gemeldeten freien Mittel übersteigt Das Portfolio.
  • Die Strategie storniert alle aktiven Orders, bevor die Positionen reduziert werden, sodass das Verhalten dem CloseAllOrders-Helfer von entspricht der ursprüngliche Fachberater.
  • Der interne Bereichspuffer speichert nur zehn Werte und entspricht der Iteration über iHigh/iLow in der Quelle EA. Nein Es sind historische Daten erforderlich, die über die letzten zehn Kerzen hinausgehen.
using System;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that detects abnormally large candles and enters in the breakout direction.
/// Uses a simple martingale recovery: after a losing trade, the next entry is taken more aggressively.
/// </summary>
public class MartingaleBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _lookback;
	private readonly StrategyParam<decimal> _breakoutMultiplier;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<DataType> _candleType;

	private readonly decimal[] _rangeBuffer = new decimal[10];
	private int _rangeBufferCount;
	private int _rangeBufferIndex;
	private decimal _rangeBufferSum;

	private decimal _entryPrice;
	private Sides? _entrySide;
	private bool _lastWasLoss;

	public int Lookback
	{
		get => _lookback.Value;
		set => _lookback.Value = value;
	}

	public decimal BreakoutMultiplier
	{
		get => _breakoutMultiplier.Value;
		set => _breakoutMultiplier.Value = value;
	}

	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public MartingaleBreakoutStrategy()
	{
		_lookback = Param(nameof(Lookback), 10)
			.SetDisplay("Lookback", "Number of candles for average range", "General");

		_breakoutMultiplier = Param(nameof(BreakoutMultiplier), 3m)
			.SetDisplay("Breakout Mult", "Multiplier above avg range for breakout", "General");

		_takeProfitPct = Param(nameof(TakeProfitPct), 1m)
			.SetDisplay("Take Profit %", "Take profit as percentage of entry price", "Trading");

		_stopLossPct = Param(nameof(StopLossPct), 0.5m)
			.SetDisplay("Stop Loss %", "Stop loss as percentage of entry price", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle type", "General");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_rangeBufferCount = 0;
		_rangeBufferIndex = 0;
		_rangeBufferSum = 0m;
		_entryPrice = 0m;
		_entrySide = null;
		_lastWasLoss = false;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var closePrice = candle.ClosePrice;

		// Check exit conditions first
		if (Position != 0 && _entryPrice > 0)
		{
			var tp = _lastWasLoss ? TakeProfitPct * 1.5m : TakeProfitPct;
			var sl = StopLossPct;

			if (_entrySide == Sides.Buy)
			{
				var pnlPct = (closePrice - _entryPrice) / _entryPrice * 100m;
				if (pnlPct >= tp || pnlPct <= -sl)
				{
					_lastWasLoss = pnlPct < 0;
					SellMarket();
					_entryPrice = 0;
					_entrySide = null;
					UpdateRangeStatistics(candle);
					return;
				}
			}
			else if (_entrySide == Sides.Sell)
			{
				var pnlPct = (_entryPrice - closePrice) / _entryPrice * 100m;
				if (pnlPct >= tp || pnlPct <= -sl)
				{
					_lastWasLoss = pnlPct < 0;
					BuyMarket();
					_entryPrice = 0;
					_entrySide = null;
					UpdateRangeStatistics(candle);
					return;
				}
			}
		}

		// Entry logic - only when flat
		if (Position == 0)
		{
			var range = candle.HighPrice - candle.LowPrice;

			if (_rangeBufferCount >= _rangeBuffer.Length)
			{
				var avgRange = _rangeBufferSum / _rangeBuffer.Length;

				if (range > avgRange * BreakoutMultiplier)
				{
					var body = candle.ClosePrice - candle.OpenPrice;

					if (body > 0 && body > range * 0.4m)
					{
						// Bullish breakout
						BuyMarket();
						_entryPrice = closePrice;
						_entrySide = Sides.Buy;
					}
					else if (body < 0 && Math.Abs(body) > range * 0.4m)
					{
						// Bearish breakout
						SellMarket();
						_entryPrice = closePrice;
						_entrySide = Sides.Sell;
					}
				}
			}
		}

		UpdateRangeStatistics(candle);
	}

	private void UpdateRangeStatistics(ICandleMessage candle)
	{
		var range = candle.HighPrice - candle.LowPrice;

		if (_rangeBufferCount < _rangeBuffer.Length)
		{
			_rangeBuffer[_rangeBufferIndex] = range;
			_rangeBufferSum += range;
			_rangeBufferCount++;
			_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
			return;
		}

		_rangeBufferSum -= _rangeBuffer[_rangeBufferIndex];
		_rangeBuffer[_rangeBufferIndex] = range;
		_rangeBufferSum += range;
		_rangeBufferIndex = (_rangeBufferIndex + 1) % _rangeBuffer.Length;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		Array.Clear(_rangeBuffer);
		_rangeBufferCount = 0;
		_rangeBufferIndex = 0;
		_rangeBufferSum = 0m;
		_entryPrice = 0m;
		_entrySide = null;
		_lastWasLoss = false;

		base.OnReseted();
	}
}