Keltner Macd Strategy
本策略结合Keltner通道和MACD指标。当价格突破上轨且MACD在信号线上方时做多;跌破下轨且MACD在信号线下方时做空。MACD与信号线出现反向交叉时离场。
测试表明年均收益约为 169%,该策略在加密市场表现最佳。
Keltner通道提供突破信号,MACD动量用于过滤方向。适合寻求波动扩张并依靠动量确认的交易者,止损基于ATR倍数。
细节
- 入场条件:
- 多头:
Close > UpperBand && MACD > Signal - 空头:
Close < LowerBand && MACD < Signal
- 多头:
- 多/空: 双向
- 离场条件: 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()