Auf GitHub ansehen

Nevalyashka Martingale-Strategie

Übersicht

Die Nevalyashka Martingale-Strategie ist eine direkte Portierung des MetaTrader 5-Expertenberaters "Nevalyashka3_1". Sie führt ein Einzelsymbol-Martingale aus, das nach Verlustgeschäften zwischen Kaufen und Verkaufen wechselt. Die Strategie beginnt immer mit dem Verkaufen und misst das Kontokapital, um zu entscheiden, ob der vorherige Handelszyklus mit Gewinn oder Verlust endete. Ein Gewinn setzt das Volumen auf die Basislotgröße zurück und behält die Richtung unverändert bei, während ein Verlust die Lotgröße multipliziert und die Richtung in einem Versuch wechselt, den Drawdown zu erholen.

Funktionsweise

  • Erster Trade – eine Short-Position wird auf der ersten abgeschlossenen Kerze mit der Basislotgröße eröffnet.
  • Kapitalverfolgung – die Strategie speichert den höchsten beobachteten Kapitalwert. Wenn keine Position offen ist, vergleicht sie das aktuelle Kapital mit dem gespeicherten Höchststand.
    • Wenn das Kapital ein neues Hoch markierte, verwendet der nächste Trade die Basislotgröße und wiederholt die letzte Richtung.
    • Wenn das Kapital kein neues Hoch markierte, erhöht der nächste Trade den Lot mit dem Multiplikator und wechselt die Richtung.
  • Stop Loss / Take Profit – jede Order verwendet feste Abstände, die in "Punkten" (Instrumentenschritte) definiert sind. Der Take Profit spiegelt den ursprünglichen Experten: Der Stop liegt StopLossPoints vom Einstieg entfernt und das Ziel TakeProfitPoints.
  • Trailing – sobald sich der Preis um MoveProfitPoints bewegt, wird der Stop enger gezogen. Jede Bewegung erfordert einen zusätzlichen MoveStepPoints-Puffer, damit der Stop nur dann vorrückt, wenn der Markt weiter drückt. Wenn der Stop über den Einstiegspreis hinausgeht, wird das geplante Volumen durch den Multiplikator geteilt, um den nächsten Trade wieder in Richtung Basislot zu skalieren.
  • Positionsausgang – die Position schließt sofort, wenn das Kerzenhoch/-tief den Stop- oder Take-Level erreicht. Nach dem Schließen bewertet die Strategie das Kapital und bereitet das nächste Signal vor.

Parameter

  • BaseVolume – Lotgröße für den Ersthandel und alle profitablen Zyklen (Standard 0.1).
  • VolumeMultiplier – Faktor, der nach einem Verlust angewendet wird, um den nächsten Lot zu erhöhen (Standard 1.1).
  • TakeProfitPoints – Take-Profit-Abstand gemessen in Preispunkten (Standard 94).
  • MoveProfitPoints – minimale günstige Exkursion, bevor der Trailing-Stop aktiviert wird (Standard 25).
  • MoveStepPoints – extra Puffer, der zwischen aufeinanderfolgenden Trailing-Anpassungen benötigt wird (Standard 11).
  • StopLossPoints – anfänglicher Stop-Loss-Abstand gemessen in Preispunkten (Standard 70).
  • CandleType – Zeitrahmen für das Trade-Management. Standard sind 5-Minuten-Kerzen.

Details zur Positionsverwaltung

  • Die Strategie hält _plannedVolume, um die ursprüngliche "Lot"-Variable zu spiegeln. Es ändert sich nur nach dem Schließen eines Trades oder wenn der Stop das Break-Even überschreitet.
  • AdjustVolume respektiert Börsenregeln, indem die Lotgröße an VolumeStep ausgerichtet und MinVolume/MaxVolume erzwungen wird.
  • GetPointValue repliziert die MT5-Logik für "adjusted point": Für Instrumente mit 3 oder 5 Dezimalstellen wird die Punktgröße mit 10 multipliziert, um mit ganzen Pips zu arbeiten.
  • HandleLongPosition und HandleShortPosition verwenden Kerzenhochs und -tiefs, um die Stop-Modifikation und das Ausstiegsverhalten von MT5 zu emulieren, ohne auf den Indikatorverlauf angewiesen zu sein.

Verwendungshinweise

  • Die Strategie geht davon aus, dass sie ein einziges Wertpapier handelt. Fügen Sie sie der Strategie hinzu und setzen Sie Security/Portfolio vor dem Start.
  • Da es sich um ein Martingale handelt, wächst das Risiko nach einer Verlustserie schnell. Passen Sie BaseVolume und VolumeMultiplier sorgfältig an und testen Sie mit realistischen Margensanforderungen.
  • Die Stop- und Take-Profit-Abstände werden in Instrumentenpunkten definiert. Stellen Sie sicher, dass die Sicherheitsmetadaten (PriceStep, VolumeStep, MinVolume) ausgefüllt sind, damit Offsets und Lotberechnungen mit Ihrem Broker übereinstimmen.
  • Die Trailing-Logik wirkt auf abgeschlossenen Kerzen. Intrabar-Stop-Treffer können im Live-Trading früher auftreten, abhängig vom Preisweg.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Equity-based martingale strategy that alternates trade direction after losses.
/// Opens a short position on startup, resets volume after profitable cycles,
/// and increases exposure following drawdowns while managing fixed stops and targets.
/// </summary>
public class NevalyashkaMartingaleStrategy : Strategy
{
	private readonly StrategyParam<decimal> _baseVolume;
	private readonly StrategyParam<decimal> _volumeMultiplier;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _moveProfitPoints;
	private readonly StrategyParam<decimal> _moveStepPoints;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _plannedVolume;
	private decimal _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takePrice;
	private decimal _equityPeak;
	private bool _nextDirectionIsSell = true;
	private bool _initialOrderPlaced;

	/// <summary>
	/// Initializes <see cref="NevalyashkaMartingaleStrategy"/>.
	/// </summary>
	public NevalyashkaMartingaleStrategy()
	{
		_baseVolume = Param(nameof(BaseVolume), 0.1m)
			.SetDisplay("Base Volume", "Initial trade volume", "Risk")
			.SetGreaterThanZero()
			;

		_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.1m)
			.SetDisplay("Volume Multiplier", "Multiplier applied after losses", "Risk")
			.SetGreaterThanZero()
			;

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetDisplay("Take Profit Points", "Profit target in points", "Orders")
			.SetGreaterThanZero()
			;

		_moveProfitPoints = Param(nameof(MoveProfitPoints), 100m)
			.SetDisplay("Move Profit Points", "Profit buffer before trailing activates", "Orders")
			.SetGreaterThanZero()
			;

		_moveStepPoints = Param(nameof(MoveStepPoints), 50m)
			.SetDisplay("Move Step Points", "Extra buffer for trailing stop updates", "Orders")
			.SetGreaterThanZero()
			;

		_stopLossPoints = Param(nameof(StopLossPoints), 400m)
			.SetDisplay("Stop Loss Points", "Initial protective distance", "Orders")
			.SetGreaterThanZero()
			;

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

	/// <summary>
	/// Base order volume.
	/// </summary>
	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

	/// <summary>
	/// Multiplier applied when recovering from a loss.
	/// </summary>
	public decimal VolumeMultiplier
	{
		get => _volumeMultiplier.Value;
		set => _volumeMultiplier.Value = value;
	}

	/// <summary>
	/// Take profit distance in points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	/// <summary>
	/// Minimum profit in points before the stop is tightened.
	/// </summary>
	public decimal MoveProfitPoints
	{
		get => _moveProfitPoints.Value;
		set => _moveProfitPoints.Value = value;
	}

	/// <summary>
	/// Additional margin in points required between stop adjustments.
	/// </summary>
	public decimal MoveStepPoints
	{
		get => _moveStepPoints.Value;
		set => _moveStepPoints.Value = value;
	}

	/// <summary>
	/// Initial stop loss distance in points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Candle type used to drive the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_plannedVolume = 0m;
		_entryPrice = 0m;
		_stopPrice = null;
		_takePrice = null;
		_equityPeak = 0m;
		_nextDirectionIsSell = true;
		_initialOrderPlaced = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_equityPeak = Portfolio?.CurrentValue ?? 0m;
		_plannedVolume = AdjustVolume(BaseVolume);

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

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

		var point = GetPointValue();

		HandleOpenPosition(candle, point);

		if (Position != 0)
			return;

		var equity = Portfolio?.CurrentValue ?? 0m;

		if (!_initialOrderPlaced)
		{
			if (_plannedVolume == 0m)
			{
				_plannedVolume = AdjustVolume(BaseVolume);
				if (_plannedVolume == 0m)
					return;
			}

			if (OpenPosition(true, candle.ClosePrice, point))
			{
				_initialOrderPlaced = true;
				_nextDirectionIsSell = true;
			}

			return;
		}

		if (equity > _equityPeak)
		{
			_equityPeak = equity;
			_plannedVolume = AdjustVolume(BaseVolume);

			if (_plannedVolume == 0m)
				return;

			if (_nextDirectionIsSell)
			{
				if (OpenPosition(true, candle.ClosePrice, point))
					return;
			}
			else
			{
				if (OpenPosition(false, candle.ClosePrice, point))
					return;
			}
		}
		else
		{
			var increased = VolumeMultiplier > 0m ? AdjustVolume(_plannedVolume * VolumeMultiplier) : 0m;

			if (increased == 0m)
				return;

			_plannedVolume = increased;

			if (_nextDirectionIsSell)
			{
				if (OpenPosition(false, candle.ClosePrice, point))
					_nextDirectionIsSell = false;
			}
			else
			{
				if (OpenPosition(true, candle.ClosePrice, point))
					_nextDirectionIsSell = true;
			}
		}
	}

	private void HandleOpenPosition(ICandleMessage candle, decimal point)
	{
		if (Position > 0)
		{
			HandleLongPosition(candle, point);
		}
		else if (Position < 0)
		{
			HandleShortPosition(candle, point);
		}
	}

	private void HandleLongPosition(ICandleMessage candle, decimal point)
	{
		if (_stopPrice is not decimal currentStop || _takePrice is not decimal currentTake)
			return;

		var price = candle.ClosePrice;
		var moveThreshold = MoveProfitPoints * point;

		if (price - _entryPrice > moveThreshold)
		{
			var candidate = price - (StopLossPoints + MoveStepPoints) * point;

			if (candidate > currentStop)
			{
				var newStop = price - StopLossPoints * point;
				_stopPrice = newStop;

				if (_plannedVolume > AdjustVolume(BaseVolume) && newStop > _entryPrice)
					ReduceVolume();
			}
		}

		if (candle.LowPrice <= _stopPrice)
		{
			SellMarket();
			ResetProtection();
			return;
		}

		if (candle.HighPrice >= currentTake)
		{
			SellMarket();
			ResetProtection();
		}
	}

	private void HandleShortPosition(ICandleMessage candle, decimal point)
	{
		if (_stopPrice is not decimal currentStop || _takePrice is not decimal currentTake)
			return;

		var price = candle.ClosePrice;
		var moveThreshold = MoveProfitPoints * point;

		if (_entryPrice - price > moveThreshold)
		{
			var candidate = price + (StopLossPoints + MoveStepPoints) * point;

			if (candidate < currentStop)
			{
				var newStop = price + StopLossPoints * point;
				_stopPrice = newStop;

				if (_plannedVolume > AdjustVolume(BaseVolume) && newStop < _entryPrice)
					ReduceVolume();
			}
		}

		if (candle.HighPrice >= _stopPrice)
		{
			BuyMarket();
			ResetProtection();
			return;
		}

		if (candle.LowPrice <= currentTake)
		{
			BuyMarket();
			ResetProtection();
		}
	}

	private bool OpenPosition(bool isSell, decimal price, decimal point)
	{
		if (_plannedVolume <= 0m)
			return false;

		if (point <= 0m)
			return false;

		var stopOffset = StopLossPoints * point;
		var takeOffset = TakeProfitPoints * point;

		if (stopOffset <= 0m || takeOffset <= 0m)
			return false;

		if (isSell)
		{
			SellMarket();
			_stopPrice = price + stopOffset;
			_takePrice = price - takeOffset;
		}
		else
		{
			BuyMarket();
			_stopPrice = price - stopOffset;
			_takePrice = price + takeOffset;
		}

		_entryPrice = price;
		return true;
	}

	private void ReduceVolume()
	{
		if (VolumeMultiplier <= 0m)
			return;

		var baseVolume = AdjustVolume(BaseVolume);

		if (baseVolume == 0m)
			return;

		var reduced = AdjustVolume(_plannedVolume / VolumeMultiplier);

		if (reduced < baseVolume)
			reduced = baseVolume;

		_plannedVolume = reduced;
	}

	private void ResetProtection()
	{
		_stopPrice = null;
		_takePrice = null;
		_entryPrice = 0m;
	}

	private decimal AdjustVolume(decimal volume)
	{
		if (Security is null)
			return volume;

		if (volume <= 0m)
			return 0m;

		var step = Security.VolumeStep ?? 0m;

		if (step > 0m)
			volume = Math.Floor(volume / step) * step;

		var min = Security.MinVolume ?? 0m;
		if (min > 0m && volume < min)
			return 0m;

		var max = Security.MaxVolume ?? 0m;
		if (max > 0m && volume > max)
			volume = max;

		return volume;
	}

	private decimal GetPointValue()
	{
		var step = Security?.PriceStep ?? 0m;

		if (step <= 0m)
			step = 1m;

		if (step <= 0m)
			return 1m;

		var digits = 0;
		var value = step;

		while (value < 1m && digits < 10)
		{
			value *= 10m;
			digits++;
		}

		if (digits == 3 || digits == 5)
			step *= 10m;

		return step;
	}
}