Auf GitHub ansehen

Donchian-Kanal-Umkehr-Strategie

Donchian Channels markieren die jüngsten Hochs und Tiefs über einen gewählten Zeitraum. Preise, die diese Grenzen durchbrechen und dann umkehren, können auf Erschöpfung hinweisen. Diese Strategie beobachtet Schlusskurse, die nach einem kurzen Ausbruch wieder in den Kanal zurückkehren.

Tests zeigen eine durchschnittliche Jahresrendite von etwa 157%. Am besten funktioniert die Strategie auf dem Kryptomarkt.

Wenn der vorherige Schlusskurs unterhalb des unteren Bandes lag und der aktuelle Schlusskurs wieder darüber steigt, wird ein Long-Trade eingegangen. Umgekehrt, wenn der vorherige Schlusskurs oberhalb des oberen Bandes war und der Preis wieder innerhalb fällt, wird ein Short eröffnet. In beiden Fällen verwaltet ein prozentualer Stop das Risiko.

Indem nur nach einem gescheiterten Ausbruch gehandelt wird, versucht dieser Ansatz, falsche Bewegungen zu erfassen, die sich schnell wieder zurückziehen.

Details

  • Einstiegskriterien: Preis schließt nach dem Durchbrechen des oberen oder unteren Bandes wieder innerhalb des Donchian Channel.
  • Long/Short: Beide.
  • Ausstiegskriterien: Stop-Loss.
  • Stops: Ja, prozentbasiert.
  • Standardwerte:
    • Period = 20
    • StopLossPercent = 2
    • CandleType = 15 minute
  • Filter:
    • Kategorie: Umkehr
    • Richtung: Beide
    • Indikatoren: Donchian Channel
    • Stops: Ja
    • Komplexität: Grundlegend
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
using System;
using System.Collections.Generic;
using System.Linq;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Donchian Reversal strategy.
/// A breakout is a close beyond the channel of the Period candles before it. When the next close returns above the lower band
/// that was broken the position turns long, and when it returns below a broken upper band it turns short. A percent stop limits the loss.
/// </summary>
public class DonchianReversalStrategy : Strategy
{
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<decimal> _stopLossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<(decimal High, decimal Low)> _candles = [];
	// The previous close and the channel it was compared with.
	private (decimal Close, decimal Upper, decimal Lower)? _previous;

	/// <summary>
	/// Donchian Channel period.
	/// </summary>
	public int Period
	{
		get => _period.Value;
		set => _period.Value = value;
	}

	/// <summary>
	/// Stop-loss percentage.
	/// </summary>
	public decimal StopLossPercent
	{
		get => _stopLossPercent.Value;
		set => _stopLossPercent.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public DonchianReversalStrategy()
	{
		_period = Param(nameof(Period), 20)
			.SetGreaterThanZero()
			.SetDisplay("Period", "Donchian Channel period", "Indicators");

		_stopLossPercent = Param(nameof(StopLossPercent), 2m)
			.SetNotNegative()
			.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
		_previous = null;
	}

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

		_candles.Clear();
		_previous = null;

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

		StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);

		// The stop has to see prices between candles, not only at their close.
		foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
		{
			var quotes = new Subscription(DataType.Level1, Security);
			quotes.MarketData.BuildField = field;
			SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
		}

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

	private void ObserveProtectionQuote(Level1ChangeMessage quote)
	{
		// The high-level handler activates native protection before this callback, also between signal bars.
	}

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

		var close = candle.ClosePrice;
		var previous = _previous;

		// The channel of the candles before this one, for the next candle to compare with.
		_previous = _candles.Count == Period
			? (close, _candles.Max(c => c.High), _candles.Min(c => c.Low))
			: null;

		_candles.Add((candle.HighPrice, candle.LowPrice));

		if (_candles.Count > Period)
			_candles.RemoveAt(0);

		if (previous is not { } prior || !IsFormedAndOnlineAndAllowTrading())
			return;

		if (prior.Close < prior.Lower && close > prior.Lower && Position <= 0)
			BuyMarket(Volume + Math.Abs(Position));
		else if (prior.Close > prior.Upper && close < prior.Upper && Position >= 0)
			SellMarket(Volume + Math.Abs(Position));
	}
}