Auf GitHub ansehen

Breakeven-Trailing-Stop-Strategie

Strategie, die zeigt, wie man den Stop-Loss auf Breakeven verschiebt und ihn dann mit dem Preisanstieg verfolgt. Die Strategie eröffnet eine Long-Position und verwaltet sie in zwei Phasen:

  1. Nachdem der Preis BreakevenPlus Punkte gewonnen hat, wird der Stop auf BreakevenStep Punkte über den Einstiegspreis verschoben.
  2. Wenn der Preis mit TrailingPlus Punkten Gewinn über dem Stop weitergeht, verfolgt der Stop den Preis in einem Abstand von TrailingStep Punkten.

Die Logik ist symmetrisch für Short-Positionen, wenn eine manuell eröffnet wird.

Details

  • Einstiegskriterien: Eröffnet eine Long-Position auf der ersten abgeschlossenen Kerze.
  • Long/Short: Beide (Beispiel verwendet Long).
  • Ausstiegskriterien: Preis kreuzt den Trailing Stop.
  • Stops: Breakeven und Trailing Stop.
  • Standardwerte:
    • BreakevenPlus = 5
    • BreakevenStep = 2
    • TrailingPlus = 3
    • TrailingStep = 1
    • CandleType = TimeSpan.FromMinutes(1).TimeFrame()
  • Filter:
    • Kategorie: Stop-Verwaltung
    • Richtung: Beide
    • Indikatoren: Keine
    • Stops: Breakeven, Trailing
    • Komplexität: Grundlegend
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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>
/// Strategy that moves stop-loss to breakeven and then trails it as price advances.
/// Uses EMA crossover for entry signals with breakeven + trailing stop management.
/// </summary>
public class BreakevenTrailingStopStrategy : Strategy
{
	private readonly StrategyParam<decimal> _breakevenPercent;
	private readonly StrategyParam<decimal> _breakevenOffset;
	private readonly StrategyParam<decimal> _trailingActivation;
	private readonly StrategyParam<decimal> _trailingDistance;
	private readonly StrategyParam<int> _fastEmaPeriod;
	private readonly StrategyParam<int> _slowEmaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private decimal _stopPrice;
	private bool _breakevenReached;

	/// <summary>
	/// Profit percent before moving stop to breakeven.
	/// </summary>
	public decimal BreakevenPercent
	{
		get => _breakevenPercent.Value;
		set => _breakevenPercent.Value = value;
	}

	/// <summary>
	/// Stop offset percent above entry after breakeven activation.
	/// </summary>
	public decimal BreakevenOffset
	{
		get => _breakevenOffset.Value;
		set => _breakevenOffset.Value = value;
	}

	/// <summary>
	/// Profit percent above breakeven stop required before trailing starts.
	/// </summary>
	public decimal TrailingActivation
	{
		get => _trailingActivation.Value;
		set => _trailingActivation.Value = value;
	}

	/// <summary>
	/// Distance percent between current price and trailing stop.
	/// </summary>
	public decimal TrailingDistance
	{
		get => _trailingDistance.Value;
		set => _trailingDistance.Value = value;
	}

	/// <summary>
	/// Fast EMA period for entry signals.
	/// </summary>
	public int FastEmaPeriod
	{
		get => _fastEmaPeriod.Value;
		set => _fastEmaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period for entry signals.
	/// </summary>
	public int SlowEmaPeriod
	{
		get => _slowEmaPeriod.Value;
		set => _slowEmaPeriod.Value = value;
	}

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

	/// <summary>
	/// Initializes a new instance of the strategy.
	/// </summary>
	public BreakevenTrailingStopStrategy()
	{
		_breakevenPercent = Param(nameof(BreakevenPercent), 0.5m)
			.SetGreaterThanZero()
			.SetDisplay("Breakeven %", "Profit percent before breakeven", "Trading");

		_breakevenOffset = Param(nameof(BreakevenOffset), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Breakeven Offset", "Stop offset percent after breakeven", "Trading");

		_trailingActivation = Param(nameof(TrailingActivation), 0.3m)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Activation", "Profit percent above stop before trailing", "Trading");

		_trailingDistance = Param(nameof(TrailingDistance), 0.3m)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Distance", "Percent from price to trailing stop", "Trading");

		_fastEmaPeriod = Param(nameof(FastEmaPeriod), 8)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for updates", "General");
	}

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

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

		var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(fastEma, slowEma, ProcessCandle)
			.Start();
	}

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

		var close = candle.ClosePrice;

		if (Position == 0)
		{
			_breakevenReached = false;

			if (fastEma > slowEma)
			{
				_entryPrice = close;
				_stopPrice = close * (1m - 1m / 100m);
				BuyMarket();
			}
			else if (fastEma < slowEma)
			{
				_entryPrice = close;
				_stopPrice = close * (1m + 1m / 100m);
				SellMarket();
			}

			return;
		}

		if (Position > 0)
		{
			if (!_breakevenReached)
			{
				if (close >= _entryPrice * (1m + BreakevenPercent / 100m))
				{
					_stopPrice = _entryPrice * (1m + BreakevenOffset / 100m);
					_breakevenReached = true;
				}
			}
			else
			{
				var trailingStop = close * (1m - TrailingDistance / 100m);
				if (close >= _stopPrice * (1m + TrailingActivation / 100m) && trailingStop > _stopPrice)
				{
					_stopPrice = trailingStop;
				}
			}

			if (candle.LowPrice <= _stopPrice)
			{
				SellMarket();
			}
			else if (fastEma < slowEma)
			{
				SellMarket();
			}
		}
		else if (Position < 0)
		{
			if (!_breakevenReached)
			{
				if (close <= _entryPrice * (1m - BreakevenPercent / 100m))
				{
					_stopPrice = _entryPrice * (1m - BreakevenOffset / 100m);
					_breakevenReached = true;
				}
			}
			else
			{
				var trailingStop = close * (1m + TrailingDistance / 100m);
				if (close <= _stopPrice * (1m - TrailingActivation / 100m) && trailingStop < _stopPrice)
				{
					_stopPrice = trailingStop;
				}
			}

			if (candle.HighPrice >= _stopPrice)
			{
				BuyMarket();
			}
			else if (fastEma > slowEma)
			{
				BuyMarket();
			}
		}
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0m;
		_stopPrice = 0m;
		_breakevenReached = false;
	}
}