Keltner Macd Strategy
Стратегия основана на каналах Кельтнера и MACD. Вход в длинную позицию, когда цена пробивает верхнюю границу канала Кельтнера и MACD > сигнальной. Вход в короткую, когда цена пробивает нижнюю границу канала и MACD < сигнальной. Выход, когда MACD пересекает сигнальную линию в противоположную сторону.
Тестирование показывает среднегодичную доходность около 169%. Стратегию лучше запускать на крипторынке.
Пробои канала Кельтнера служат триггером, а импульс MACD фильтрует направление. Сделки открываются, когда оба сигнала совпадают.
Подходит трейдерам, охотящимся за расширением волатильности при поддержке импульса. Стоп на основе ATR сдерживает риск.
Детали
- Критерии входа:
- Long:
Close > UpperBand && MACD > Signal - Short:
Close < LowerBand && MACD < Signal
- Long:
- Long/Short: Оба направления
- Критерии выхода: пересечение MACD в противоположную сторону
- Стопы: ATR на основе
AtrMultiplier - Значения по умолчанию:
EmaPeriod= 20Multiplier= 2mAtrPeriod= 14MacdFastPeriod= 12MacdSlowPeriod= 26MacdSignalPeriod= 9AtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(15).TimeFrame()
- Фильтры:
- Категория: Mean reversion
- Направление: Оба
- Индикаторы: Keltner Channel, MACD
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class keltner_macd_strategy(Strategy):
"""
Strategy based on Keltner Channels and MACD.
Enters long when price breaks above upper Keltner Channel with MACD cross above Signal.
Enters short when price breaks below lower Keltner Channel with MACD cross below Signal.
Exits when MACD crosses its signal line in the opposite direction.
"""
def __init__(self):
super(keltner_macd_strategy, self).__init__()
self._emaPeriod = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "Period for EMA calculation in Keltner Channel", "Indicators")
self._multiplier = self.Param("Multiplier", 2.0) \
.SetDisplay("ATR Multiplier", "ATR multiplier for Keltner Channel bands", "Indicators")
self._atrPeriod = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR calculation in Keltner Channel", "Indicators")
self._macdFastPeriod = self.Param("MacdFastPeriod", 12) \
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD calculation", "Indicators")
self._macdSlowPeriod = self.Param("MacdSlowPeriod", 26) \
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD calculation", "Indicators")
self._macdSignalPeriod = self.Param("MacdSignalPeriod", 9) \
.SetDisplay("MACD Signal Period", "Signal line period for MACD calculation", "Indicators")
self._cooldownBars = self.Param("CooldownBars", 20) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._atrMultiplier = self.Param("AtrMultiplier", 2.0) \
.SetDisplay("Stop Loss ATR Multiplier", "ATR multiplier for stop loss calculation", "Risk Management")
self._candleType = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Timeframe of data for strategy", "General")
self._stopLossPercent = self.Param("StopLossPercent", 1.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
self._prevMacd = 0.0
self._prevSignal = 0.0
self._cooldown = 0
@property
def CandleType(self):
return self._candleType.Value
def OnReseted(self):
super(keltner_macd_strategy, self).OnReseted()
self._prevMacd = 0.0
self._prevSignal = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(keltner_macd_strategy, self).OnStarted2(time)
self._prevMacd = 0.0
self._prevSignal = 0.0
self._cooldown = 0
ema = ExponentialMovingAverage()
ema.Length = self._emaPeriod.Value
atr = AverageTrueRange()
atr.Length = self._atrPeriod.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macdFastPeriod.Value
macd.Macd.LongMa.Length = self._macdSlowPeriod.Value
macd.SignalMa.Length = self._macdSignalPeriod.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(ema, atr, macd, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
macdArea = self.CreateChartArea()
if macdArea is not None:
self.DrawIndicator(macdArea, macd)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ema_value, atr_value, macd_value):
if candle.State != CandleStates.Finished:
return
ema = float(ema_value)
atr = float(atr_value)
multiplier = float(self._multiplier.Value)
upperBand = ema + multiplier * atr
lowerBand = ema - multiplier * atr
if macd_value.Macd is None or macd_value.Signal is None:
return
macd = float(macd_value.Macd)
signal = float(macd_value.Signal)
macdCrossedAboveSignal = self._prevMacd <= self._prevSignal and macd > signal
macdCrossedBelowSignal = self._prevMacd >= self._prevSignal and macd < signal
if not self.IsFormedAndOnlineAndAllowTrading():
self._prevMacd = macd
self._prevSignal = signal
return
if self._cooldown > 0:
self._cooldown -= 1
cooldown_val = int(self._cooldownBars.Value)
if self._cooldown == 0 and float(candle.ClosePrice) > upperBand * 1.001 and macdCrossedAboveSignal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
elif self._cooldown == 0 and float(candle.ClosePrice) < lowerBand * 0.999 and macdCrossedBelowSignal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._cooldown = cooldown_val
if self.Position > 0 and macdCrossedBelowSignal:
self.ClosePosition()
self._cooldown = cooldown_val
elif self.Position < 0 and macdCrossedAboveSignal:
self.ClosePosition()
self._cooldown = cooldown_val
self._prevMacd = macd
self._prevSignal = signal
def CreateClone(self):
return keltner_macd_strategy()