Auf GitHub ansehen

Exp Martin V2-Strategie

Die Exp Martin V2-Strategie implementiert einen exponentiellen Martingale-Ansatz. Es wird immer nur eine einzige offene Position gehalten, und nach jedem Trade wird die nächste Richtung und das Volumen basierend auf dem Gewinn des letzten Geschäfts entschieden.

Die Strategie beginnt mit einem vordefinierten Auftragstyp (Kauf oder Verkauf) und einem Startvolumen. Jede Position erhält einen festen Take-Profit und Stop-Loss. Wenn ein Trade mit Gewinn endet, wird eine neue Position gleicher Art mit dem Startvolumen eröffnet. Endet der Trade mit Verlust, wird die Richtung umgekehrt und das Volumen mit einem angegebenen Faktor multipliziert. Die Multiplikation setzt sich nach jedem Verlust fort, bis eine maximale Anzahl von Multiplikationen erreicht ist; dann wird das Volumen auf den Startwert zurückgesetzt.

Dies erzeugt eine eskalierende Sequenz entgegengesetzter Trades, die darauf abzielt, frühere Verluste zu erholen, sobald eine profitable Bewegung eintritt.

Details

  • Einstiegslogik:
    • Anfangsposition gemäß Start Type (0 - Kauf, 1 - Verkauf) mit dem Start Volume eröffnen.
    • Nach einem profitablen Trade dieselbe Richtung mit dem Startvolumen wiederholen.
    • Nach einem verlustbringenden Trade die Richtung umkehren und das Volumen mit Factor multiplizieren, bis Limit Multiplikationen erreicht sind.
  • Long/Short: Beide, abhängig von der aktuellen Sequenz.
  • Ausstiegslogik:
    • Positionen werden geschlossen, wenn der Preis die konfigurierten Take Profit- oder Stop Loss-Niveaus erreicht.
  • Stops: Feste Stop-Loss und Take-Profit in Punkten.
  • Filter: Keine.
  • Positionsverwaltung: Nur eine Position ist gleichzeitig offen.

Verwenden Sie diese Strategie, um mit Martingale-Geldmanagement in StockSharp ohne zusätzliche Indikatoren zu experimentieren.

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>
/// Exponential martingale strategy.
/// </summary>
public class ExpMartinV2Strategy : Strategy
{
	private readonly StrategyParam<decimal> _startVolume;
	private readonly StrategyParam<decimal> _factor;
	private readonly StrategyParam<int> _limit;
	private readonly StrategyParam<int> _stopLoss;
	private readonly StrategyParam<int> _takeProfit;
	private readonly StrategyParam<int> _startType;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cycleCooldownBars;

	private decimal _currentVolume;
	private decimal _maxVolume;
	private bool _needOpenPosition = true;
	private int _direction;
	private decimal _entryPrice;
	private decimal _longTake;
	private decimal _longStop;
	private decimal _shortTake;
	private decimal _shortStop;
	private int _cooldownRemaining;

	/// <summary>
	/// Initial order volume.
	/// </summary>
	public decimal StartVolume { get => _startVolume.Value; set => _startVolume.Value = value; }

	/// <summary>
	/// Volume multiplier after a loss.
	/// </summary>
	public decimal Factor { get => _factor.Value; set => _factor.Value = value; }

	/// <summary>
	/// Maximum number of volume multiplications.
	/// </summary>
	public int Limit { get => _limit.Value; set => _limit.Value = value; }

	/// <summary>
	/// Stop loss in price steps.
	/// </summary>
	public int StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }

	/// <summary>
	/// Take profit in price steps.
	/// </summary>
	public int TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }

	/// <summary>
	/// Starting order type: 0 - buy, 1 - sell.
	/// </summary>
	public int StartType { get => _startType.Value; set => _startType.Value = value; }

	/// <summary>
	/// Candle type used for processing.
	/// </summary>
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	/// <summary>
	/// Closed candles to wait before starting the next martingale cycle.
	/// </summary>
	public int CycleCooldownBars { get => _cycleCooldownBars.Value; set => _cycleCooldownBars.Value = value; }

	public ExpMartinV2Strategy()
	{
		_startVolume = Param(nameof(StartVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Start Volume", "Initial order volume", "General");

		_factor = Param(nameof(Factor), 2m)
			.SetGreaterThanZero()
			.SetDisplay("Factor", "Volume multiplier", "General");

		_limit = Param(nameof(Limit), 5)
			.SetGreaterThanZero()
			.SetDisplay("Limit", "Max multiplication count", "General");

		_stopLoss = Param(nameof(StopLoss), 100)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss", "Loss limit in points", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 100)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit", "Profit target in points", "Risk");

		_startType = Param(nameof(StartType), 0)
			.SetDisplay("Start Type", "0-Buy, 1-Sell", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");

		_cycleCooldownBars = Param(nameof(CycleCooldownBars), 2)
			.SetNotNegative()
			.SetDisplay("Cycle Cooldown Bars", "Closed candles to wait before the next entry cycle", "General");
	}

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

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

		_currentVolume = 0m;
		_maxVolume = 0m;
		_needOpenPosition = true;
		_direction = 0;
		_entryPrice = 0m;
		_longTake = 0m;
		_longStop = 0m;
		_shortTake = 0m;
		_shortStop = 0m;
		_cooldownRemaining = 0;
	}

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

		StartProtection(null, null);

		_currentVolume = StartVolume;
		_direction = StartType == 0 ? 1 : -1;

		_maxVolume = StartVolume;
		for (var i = 0; i < Limit; i++)
			_maxVolume = RoundVolume(_maxVolume * Factor);

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

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var step = Security.PriceStep ?? 1m;

		if (Position > 0)
		{
			if (candle.HighPrice >= _longTake)
			{
				SellMarket(Position);
				PrepareNext(true);
			}
			else if (candle.LowPrice <= _longStop)
			{
				SellMarket(Position);
				PrepareNext(false);
			}
		}
		else if (Position < 0)
		{
			if (candle.LowPrice <= _shortTake)
			{
				BuyMarket(-Position);
				PrepareNext(true);
			}
			else if (candle.HighPrice >= _shortStop)
			{
				BuyMarket(-Position);
				PrepareNext(false);
			}
		}

		if (_needOpenPosition && Position == 0 && _cooldownRemaining == 0)
		{
			_entryPrice = candle.ClosePrice;
			if (_direction == 1)
			{
				BuyMarket(_currentVolume);
				_longTake = _entryPrice + TakeProfit * step;
				_longStop = _entryPrice - StopLoss * step;
			}
			else
			{
				SellMarket(_currentVolume);
				_shortTake = _entryPrice - TakeProfit * step;
				_shortStop = _entryPrice + StopLoss * step;
			}
			_needOpenPosition = false;
		}
	}

	private void PrepareNext(bool wasProfit)
	{
		if (wasProfit)
		{
			_currentVolume = StartVolume;
		}
		else
		{
			_direction = -_direction;
			_currentVolume = RoundVolume(_currentVolume * Factor);
			if (_currentVolume > _maxVolume)
				_currentVolume = StartVolume;
		}

		_needOpenPosition = true;
		_cooldownRemaining = CycleCooldownBars;
	}

	private decimal RoundVolume(decimal volume)
	{
		var step = Security.VolumeStep ?? 1m;
		return Math.Ceiling(volume / step) * step;
	}
}