Ver en GitHub

Keltner Macd Strategy

Strategy based on Keltner Channels and MACD. Enters long when price breaks above upper Keltner Channel with MACD > Signal. Enters short when price breaks below lower Keltner Channel with MACD < Signal. Exits when MACD crosses its signal line in the opposite direction.

Testing indicates an average annual return of about 169%. It performs best in the crypto market.

Keltner Channel breakouts serve as the trigger, and MACD momentum filters the direction. The strategy initiates trades once both signals align.

Good for traders chasing volatility expansions with momentum backing. An ATR-based stop contains risk.

Details

  • Entry Criteria:
    • Long: Close > UpperBand && MACD > Signal
    • Short: Close < LowerBand && MACD < Signal
  • Long/Short: Both
  • Exit Criteria: MACD cross opposite
  • Stops: ATR-based using AtrMultiplier
  • Default Values:
    • EmaPeriod = 20
    • Multiplier = 2m
    • AtrPeriod = 14
    • MacdFastPeriod = 12
    • MacdSlowPeriod = 26
    • MacdSignalPeriod = 9
    • AtrMultiplier = 2m
    • CandleType = TimeSpan.FromMinutes(15).TimeFrame()
  • Filters:
    • Category: Mean reversion
    • Direction: Both
    • Indicators: Keltner Channel, MACD
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Mid-term
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
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;

using StockSharp.Algo;
using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy based on Keltner Channels and MACD.
/// Enters long when price breaks above upper Keltner Channel with MACD > Signal.
/// Enters short when price breaks below lower Keltner Channel with MACD < Signal.
/// Exits when MACD crosses its signal line in the opposite direction.
/// </summary>
public class KeltnerMacdStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<decimal> _multiplier;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<decimal> _atrMultiplier;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _stopLossPercent;

	private ExponentialMovingAverage _ema;
	private AverageTrueRange _atr;
	private MovingAverageConvergenceDivergenceSignal _macd;
	
	private decimal _prevMacd;
	private decimal _prevSignal;
	private int _cooldown;

	/// <summary>
	/// EMA period for Keltner Channel middle line.
	/// </summary>
	public int EmaPeriod
	{
		get => _emaPeriod.Value;
		set => _emaPeriod.Value = value;
	}

	/// <summary>
	/// ATR multiplier for Keltner Channel bands.
	/// </summary>
	public decimal Multiplier
	{
		get => _multiplier.Value;
		set => _multiplier.Value = value;
	}

	/// <summary>
	/// ATR period for Keltner Channel bands.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// MACD fast EMA period.
	/// </summary>
	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	/// <summary>
	/// MACD slow EMA period.
	/// </summary>
	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	/// <summary>
	/// MACD signal line period.
	/// </summary>
	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	/// <summary>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// ATR multiplier for stop loss calculation.
	/// </summary>
	public decimal AtrMultiplier
	{
		get => _atrMultiplier.Value;
		set => _atrMultiplier.Value = value;
	}

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

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

	/// <summary>
	/// Initializes a new instance of the <see cref="KeltnerMacdStrategy"/>.
	/// </summary>
	public KeltnerMacdStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "Period for EMA calculation in Keltner Channel", "Indicators")
			
			.SetOptimize(10, 30, 5);

		_multiplier = Param(nameof(Multiplier), 2m)
			.SetDisplay("ATR Multiplier", "ATR multiplier for Keltner Channel bands", "Indicators")
			
			.SetOptimize(1.5m, 3m, 0.5m);

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "Period for ATR calculation in Keltner Channel", "Indicators");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 12)
			.SetDisplay("MACD Fast Period", "Fast EMA period for MACD calculation", "Indicators")
			;

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 26)
			.SetDisplay("MACD Slow Period", "Slow EMA period for MACD calculation", "Indicators")
			;

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 9)
			.SetDisplay("MACD Signal Period", "Signal line period for MACD calculation", "Indicators")
			;

		_cooldownBars = Param(nameof(CooldownBars), 20)
			.SetRange(1, 200)
			.SetDisplay("Cooldown Bars", "Bars between entries", "General");

		_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
			.SetDisplay("Stop Loss ATR Multiplier", "ATR multiplier for stop loss calculation", "Risk Management");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe of data for strategy", "General");

		_stopLossPercent = Param(nameof(StopLossPercent), 1.0m)
			.SetNotNegative()
			.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
			
			.SetOptimize(0.5m, 2.0m, 0.5m);
	}

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

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

		_ema?.Reset();
		_atr?.Reset();
		_macd?.Reset();

		_prevMacd = 0;
		_prevSignal = 0;
		_cooldown = 0;
	}

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

		// Create indicators
		_ema = new EMA { Length = EmaPeriod };
		_atr = new AverageTrueRange { Length = AtrPeriod };

		_macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod },
			},
			SignalMa = { Length = MacdSignalPeriod }
		};
		// Initialize variables
		// Create subscription
		var subscription = SubscribeCandles(CandleType);

		// Process candles with indicators
		subscription
			.BindEx(_ema, _atr, _macd, ProcessCandle)
			.Start();

		// Setup chart visualization
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			
			// MACD in separate area
			var macdArea = CreateChartArea();
			if (macdArea != null)
			{
				DrawIndicator(macdArea, _macd);
			}
			
			DrawOwnTrades(area);
		}

	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue, IIndicatorValue atrValue, IIndicatorValue macdValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;

		var ema = emaValue.ToDecimal();
		var atr = atrValue.ToDecimal();

		// Calculate Keltner Channels
		var upperBand = ema + Multiplier * atr;
		var lowerBand = ema - Multiplier * atr;

		var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;

		// Process MACD separately to get MACD and Signal values
		if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
		{
			return;
		}

		// Detect MACD crosses
		bool macdCrossedAboveSignal = _prevMacd <= _prevSignal && macd > signal;
		bool macdCrossedBelowSignal = _prevMacd >= _prevSignal && macd < signal;

		// Check if strategy is ready for trading
		if (!IsFormedAndOnlineAndAllowTrading())
		{
			// Store current values for next candle
			_prevMacd = macd;
			_prevSignal = signal;
			return;
		}

		// Trading logic
		if (_cooldown > 0)
			_cooldown--;

		if (_cooldown == 0 && candle.ClosePrice > upperBand * 1.001m && macdCrossedAboveSignal && Position <= 0)
		{
			// Price breaks above upper Keltner Channel with bullish MACD - go long
			BuyMarket(Volume + Math.Abs(Position));
			_cooldown = CooldownBars;
		}
		else if (_cooldown == 0 && candle.ClosePrice < lowerBand * 0.999m && macdCrossedBelowSignal && Position >= 0)
		{
			// Price breaks below lower Keltner Channel with bearish MACD - go short
			SellMarket(Volume + Math.Abs(Position));
			_cooldown = CooldownBars;
		}
		
		// Exit logic based on MACD crosses
		if (Position > 0 && macdCrossedBelowSignal)
		{
			// Exit long position when MACD crosses below Signal
			ClosePosition();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && macdCrossedAboveSignal)
		{
			// Exit short position when MACD crosses above Signal
			ClosePosition();
			_cooldown = CooldownBars;
		}

		// Store current values for next candle
		_prevMacd = macd;
		_prevSignal = signal;
	}
}