Auf GitHub ansehen

MartinGale Breakout-Strategie

Überblick

Die MartinGale Breakout Strategy ist ein Breakout-Folgesystem, das aus dem MetaTrader 4-Expertenberater MartinGaleBreakout abgeleitet wurde. Der ursprüngliche Roboter betritt Positionen, nachdem er ungewöhnlich große Kerzen erkannt hat, und wendet einen Wiederherstellungsmechanismus im Martingal-Stil an, um frühere Verluste auszugleichen. Dieser StockSharp-Port reproduziert das Verhalten mithilfe der übergeordneten Strategie API mit Kerzenabonnements und Geldverwaltungsparametern.

Die Strategie überwacht eine konfigurierbare Kerzenreihe und sucht nach Kerzen, deren Spanne mindestens dreimal größer ist als die durchschnittliche Spanne der vorherigen zehn Balken. Wenn eine solche Kerze stark in eine Richtung schließt, eröffnet die Strategie eine Marktposition in dieser Richtung. Wenn die Position mit einem Verlust geschlossen wird, der einen konfigurierbaren Schwellenwert überschreitet, erhöht der Wiederherstellungsmodus die Take-Profit-Distanz, um den realisierten Drawdown auszugleichen.

Handelslogik

  1. Abonnieren Sie die ausgewählte Kerzenserie (standardmäßig 15-Minuten-Kerzen).
  2. Behalten Sie die letzten 11 abgeschlossenen Kerzen bei, um die abnormale Volatilität zu bewerten.
  3. Erkennen Sie einen bullischen Ausbruch, wenn:
    • Die aktuelle Kerze ist dreimal größer als die durchschnittliche Spanne der vorherigen zehn Kerzen.
    • Die Kerze schließt in der oberen Hälfte ihrer Spanne.
  4. Erkennen Sie einen rückläufigen Ausbruch anhand der symmetrischen Bedingungen.
  5. Eröffnen Sie eine Marktposition in Ausbruchsrichtung, wenn:
    • Derzeit ist keine weitere Stelle offen.
    • Das geschätzte Kapitalrisiko liegt unter dem konfigurierten Saldoprozentsatz.
  6. Schließen Sie Positionen und setzen Sie Gewinn-/Verlustziele zurück, wenn:
    • Der variable Gewinn erreicht die Take-Profit-Schwelle.
    • Der schwebende Verlust erreicht die Stop-Loss-Schwelle.
  7. Wenn ein Stop-Loss auftritt, wechseln Sie in den Wiederherstellungsmodus:
    • Erhöhen Sie die Take-Profit-Distanz um den konfigurierten Multiplikator.
    • Erweitern Sie das Stop-Loss-Limit auf den maximal zulässigen Prozentsatz.
    • Setzen Sie den Handel fort, bis das nächste Ziel erreicht ist, und setzen Sie dann auf die Basiskonfiguration zurück.

Parameter

Name Beschreibung Standard
TakeProfitPoints Basis-Take-Profit-Distanz, ausgedrückt in Instrumentenpunkten. 50
BalancePercentageAvailable Maximaler Anteil des Kontostandes, der einem einzelnen Trade zugeordnet werden kann. 50%
TakeProfitBalancePercent Zielgewinn als Prozentsatz des Kontostands. 0.1%
StopLossBalancePercent Maximaler Drawdown vor Auslösung der Erholung. 10%
StartRecoveryFactor Teil des Stop-Loss, der vor der Aktivierung des Wiederherstellungsmodus verwendet wird. 0.2
TakeProfitPointsMultiplier Multiplikator, der während der Erholung auf die Take-Profit-Distanz angewendet wird. 1
CandleType Kerzenserien, die für Ausbruchsberechnungen verwendet werden. 15-minute

Positionsgrößenbestimmung und Risikokontrolle

  • Die Strategie berechnet anhand der Tick-Größe und des Tick-Werts des Instruments das erforderliche Volumen, um den konfigurierten monetären Take-Profit zu erzielen.
  • Die Volumina werden auf Austauschbeschränkungen (Schritt, Minimum, Maximum) normalisiert.
  • Das geschätzte Kapitalrisiko darf den konfigurierten Saldoprozentsatz nicht überschreiten.
  • Der Wiederherstellungsmodus erweitert das Take-Profit-Ziel nach einem Verlust dynamisch und emuliert dabei das ursprüngliche Martingal-Verhalten, während die Positionen auf einen einzigen offenen Trade beschränkt bleiben.

Notizen

  • Die Strategie basiert auf Informationen zum Portfoliogleichgewicht. Initialisieren Sie es vor dem Start mit einer Portfolio-Verbindung.
  • Die Provisionsabwicklung spiegelt die ursprüngliche EA wider, indem sie sich auf variable Gewinne und Verluste konzentriert, die aus der aktuellen Position abgeleitet werden.
  • Es werden keine ausstehenden Aufträge verwendet – Ein- und Ausstiege werden nur mit Marktaufträgen durchgeführt.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy with martingale-style recovery.
/// Detects abnormally large candles relative to recent history and enters in the breakout direction.
/// After a stop-loss, enters recovery mode with a wider take-profit target.
/// </summary>
public class MartinGaleBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _requiredHistory;
	private readonly StrategyParam<decimal> _breakoutFactor;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<decimal> _recoveryMultiplier;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _ranges = new();
	private decimal _entryPrice;
	private Sides? _entrySide;
	private bool _recovering;

	public int RequiredHistory
	{
		get => _requiredHistory.Value;
		set => _requiredHistory.Value = value;
	}

	public decimal BreakoutFactor
	{
		get => _breakoutFactor.Value;
		set => _breakoutFactor.Value = value;
	}

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

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

	public decimal RecoveryMultiplier
	{
		get => _recoveryMultiplier.Value;
		set => _recoveryMultiplier.Value = value;
	}

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

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

		_breakoutFactor = Param(nameof(BreakoutFactor), 2.5m)
			.SetDisplay("Breakout Factor", "Multiplier for abnormal candle detection", "General");

		_takeProfitPct = Param(nameof(TakeProfitPct), 0.5m)
			.SetDisplay("TP %", "Take profit percent of entry", "Trading");

		_stopLossPct = Param(nameof(StopLossPct), 0.3m)
			.SetDisplay("SL %", "Stop loss percent of entry", "Trading");

		_recoveryMultiplier = Param(nameof(RecoveryMultiplier), 1.5m)
			.SetDisplay("Recovery Mult", "TP multiplier during recovery", "Trading");

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

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

		_ranges.Clear();
		_entryPrice = 0;
		_entrySide = null;
		_recovering = 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 close = candle.ClosePrice;
		var range = candle.HighPrice - candle.LowPrice;

		// Check exit
		if (Position != 0 && _entryPrice > 0)
		{
			var tpPct = _recovering ? TakeProfitPct * RecoveryMultiplier : TakeProfitPct;

			if (_entrySide == Sides.Buy)
			{
				var pnl = (close - _entryPrice) / _entryPrice * 100m;
				if (pnl >= tpPct || pnl <= -StopLossPct)
				{
					var wasLoss = pnl < 0;
					SellMarket();
					_entryPrice = 0;
					_entrySide = null;
					_recovering = wasLoss;
					AddRange(range);
					return;
				}
			}
			else if (_entrySide == Sides.Sell)
			{
				var pnl = (_entryPrice - close) / _entryPrice * 100m;
				if (pnl >= tpPct || pnl <= -StopLossPct)
				{
					var wasLoss = pnl < 0;
					BuyMarket();
					_entryPrice = 0;
					_entrySide = null;
					_recovering = wasLoss;
					AddRange(range);
					return;
				}
			}
		}

		// Entry - only when flat
		if (Position == 0 && _ranges.Count >= RequiredHistory)
		{
			decimal sum = 0;
			for (int i = 0; i < _ranges.Count; i++)
				sum += _ranges[i];
			var avgRange = sum / _ranges.Count;

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

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

		AddRange(range);
	}

	private void AddRange(decimal range)
	{
		_ranges.Add(range);
		while (_ranges.Count > RequiredHistory)
			_ranges.RemoveAt(0);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_ranges.Clear();
		_entryPrice = 0;
		_entrySide = null;
		_recovering = false;

		base.OnReseted();
	}
}