Auf GitHub ansehen

Trendline-Bounce-Strategie

Märkte respektieren häufig Trendlinien, die durch frühere Swing-Hochs oder -Tiefs gezogen werden. Diese Strategie passt automatisch Regressionslinien an die jüngste Kursentwicklung an und sucht nach Kerzen, die von diesen Linien in Richtung des dominanten Trends abprallen.

Tests zeigen eine durchschnittliche Jahresrendite von etwa 124%. Die Strategie eignet sich am besten für den Devisenmarkt.

Jüngste Kerzen werden gespeichert, um aufwärts oder abwärts geneigte Unterstützungs- und Widerstandslinien zu berechnen. Wenn der Kurs eine Trendlinie berührt und eine Kerze den Abprall bestätigt, während sie auf der richtigen Seite eines gleitenden Durchschnitts bleibt, eröffnet das System einen Trade. Der Stop wird als Prozentsatz des Kurses gesetzt und ein Ausstieg erfolgt beim Kreuzen des gleitenden Durchschnitts.

Indem nur in der vorherrschenden Richtung gehandelt und auf eine klare Reaktion an Unterstützung oder Widerstand gewartet wird, versucht die Methode, Fortsetzungsbewegungen zu erfassen, ohne Ausbrüchen hinterherauszulaufen.

Details

  • Einstiegskriterien: Kurs berührt berechnete Trendlinie und Kerze schließt in Trendrichtung ober-/unterhalb des MA.
  • Long/Short: Beide.
  • Ausstiegskriterien: Kurs kreuzt den gleitenden Durchschnitt oder Stop-Loss.
  • Stops: Ja, prozentbasiert.
  • Standardwerte:
    • TrendlinePeriod = 20
    • MAPeriod = 20
    • BounceThresholdPercent = 0.5
    • CandleType = 5 minute
    • StopLossPercent = 2
  • Filter:
    • Kategorie: Trendfolge
    • Richtung: Beide
    • Indikatoren: MA, Trendlines
    • Stops: Ja
    • Komplexität: Fortgeschritten
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Trendline Bounce strategy.
/// Calculates linear regression of recent lows (support) and highs (resistance).
/// Buys on bounce off support trendline, sells on bounce off resistance.
/// Uses SMA for exit signals.
/// </summary>
public class TrendlineBounceStrategy : Strategy
{
	private readonly StrategyParam<int> _trendlinePeriod;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private int _cooldown;

	/// <summary>
	/// Trendline period.
	/// </summary>
	public int TrendlinePeriod
	{
		get => _trendlinePeriod.Value;
		set => _trendlinePeriod.Value = value;
	}

	/// <summary>
	/// MA Period.
	/// </summary>
	public int MAPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

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

	/// <summary>
	/// Cooldown bars.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public TrendlineBounceStrategy()
	{
		_trendlinePeriod = Param(nameof(TrendlinePeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Trendline Period", "Lookback for trendline", "Indicators");

		_maPeriod = Param(nameof(MAPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Period for SMA", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_cooldownBars = Param(nameof(CooldownBars), 500)
			.SetRange(1, 1000)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_highs.Clear();
		_lows.Clear();
		_cooldown = default;
	}

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

		_highs.Clear();
		_lows.Clear();
		_cooldown = 0;

		var sma = new SimpleMovingAverage { Length = MAPeriod };

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

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

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

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		if (_highs.Count > TrendlinePeriod)
		{
			_highs.RemoveAt(0);
			_lows.RemoveAt(0);
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_highs.Count < TrendlinePeriod)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		// Calculate linear regression for support (lows) and resistance (highs)
		var supportLevel = GetLinRegValue(_lows);
		var resistanceLevel = GetLinRegValue(_highs);
		var buffer = (resistanceLevel - supportLevel) * 0.05m;

		if (buffer <= 0)
			return;

		var isBullish = candle.ClosePrice > candle.OpenPrice;
		var isBearish = candle.ClosePrice < candle.OpenPrice;

		// Bounce off support (buy)
		if (Position == 0 && candle.LowPrice <= supportLevel + buffer && isBullish)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
		// Bounce off resistance (sell)
		else if (Position == 0 && candle.HighPrice >= resistanceLevel - buffer && isBearish)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		// Exit using SMA
		else if (Position > 0 && candle.ClosePrice < smaValue)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && candle.ClosePrice > smaValue)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
	}

	private static decimal GetLinRegValue(List<decimal> values)
	{
		var n = values.Count;
		if (n == 0) return 0;

		decimal sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
		for (int i = 0; i < n; i++)
		{
			sumX += i;
			sumY += values[i];
			sumXY += i * values[i];
			sumX2 += i * i;
		}

		var denom = n * sumX2 - sumX * sumX;
		if (denom == 0) return sumY / n;

		var slope = (n * sumXY - sumX * sumY) / denom;
		var intercept = (sumY - slope * sumX) / n;

		return slope * (n - 1) + intercept;
	}
}