Auf GitHub ansehen

Karpenko-Kanal-Strategie

Die Karpenko-Kanal-Strategie erstellt einen dynamischen Preiskanal mithilfe zweier gleitender Durchschnitte. Die Basislinie ist ein Durchschnitt der Schlusskurse, während die oberen und unteren Grenzen aus dem durchschnittlichen Hoch-Tief-Bereich abgeleitet werden, skaliert mit dem goldenen Verhältnis 1.618. Der Kanal erweitert sich, bis er den aktuellen Balken umschließt.

Ein Signal für Long erscheint, wenn die obere Grenze, die zuvor über der Basislinie lag, diese von oben nach unten kreuzt. Ein Short-Signal entsteht, wenn die obere Grenze die Basislinie von unten nach oben kreuzt, nachdem sie darunter war. Bestehende Positionen in entgegengesetzter Richtung werden bei einem Regimewechsel geschlossen.

Es werden nur abgeschlossene Kerzen verarbeitet. Feste Stop-Loss- und Take-Profit-Niveaus schützen jeden Trade.

Details

  • Einstiegskriterien:
    • Long: Vorherige obere Grenze über der Basislinie und aktueller Wert darunter oder gleich.
    • Short: Vorherige obere Grenze unter der Basislinie und aktueller Wert darüber oder gleich.
  • Ausstiegskriterien:
    • Long schließen, wenn die vorherige obere Grenze unter der Basislinie war.
    • Short schließen, wenn die vorherige obere Grenze über der Basislinie war.
  • Stops: Feste Stop-Loss- und Take-Profit-Abstände in Preiseinheiten.
  • Standardwerte:
    • Base MA = 144
    • History = 500
    • Stop Loss = 1000
    • Take Profit = 2000
    • Candle Type = 4 hour
  • Filter:
    • Kategorie: Trendfolge
    • Richtung: Beide
    • Indikatoren: Custom
    • Stops: Ja
    • Komplexität: Mittel
    • Zeitrahmen: Mittelfristig
    • 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>
/// Karpenko Channel strategy.
/// Generates signals based on dynamic channel and SMA baseline crossover.
/// Long when price is below channel baseline, short when above.
/// </summary>
public class KarpenkoChannelStrategy : Strategy
{
	private readonly StrategyParam<int> _basicMa;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMa;
	private bool _initialized;
	private int _cooldownRemaining;

	/// <summary>
	/// Period for base moving average.
	/// </summary>
	public int BasicMa { get => _basicMa.Value; set => _basicMa.Value = value; }

	/// <summary>
	/// Number of completed candles to wait after a position change.
	/// </summary>
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }

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

	public KarpenkoChannelStrategy()
	{
		_basicMa = Param(nameof(BasicMa), 20)
			.SetGreaterThanZero()
			.SetDisplay("Base MA", "Length of base moving average", "Parameters");

		_cooldownBars = Param(nameof(CooldownBars), 8)
			.SetDisplay("Cooldown Bars", "Completed candles to wait after a signal", "Signal");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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 OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var sma = new SimpleMovingAverage { Length = BasicMa };

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

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

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

		_prevClose = 0m;
		_prevMa = 0m;
		_initialized = false;
		_cooldownRemaining = 0;
	}

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

		if (!_initialized)
		{
			_prevClose = candle.ClosePrice;
			_prevMa = maValue;
			_initialized = true;
			return;
		}

		// Cross above MA -> buy signal
		var crossUp = _prevClose <= _prevMa && candle.ClosePrice > maValue;
		// Cross below MA -> sell signal
		var crossDown = _prevClose >= _prevMa && candle.ClosePrice < maValue;

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		if (crossUp && _cooldownRemaining == 0 && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownBars;
		}
		else if (crossDown && _cooldownRemaining == 0 && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownBars;
		}

		_prevClose = candle.ClosePrice;
		_prevMa = maValue;
	}
}