Прорыв по ширине канала Келтнера
Стратегия Keltner Channel Width Breakout наблюдает за быстрым расширением канала Келтнера. Когда значения выходят за пределы типичного диапазона, цена часто начинает новое движение.
Тестирование показывает среднегодичную доходность около 112%. Стратегию лучше запускать на рынке Форекс.
Позиция открывается, как только индикатор пробивает полосу, построенную по последним данным и множителю отклонения. Возможны сделки в обе стороны со стопом.
Система подходит трейдерам импульсных стратегий, стремящимся поймать ранние прорывы. Сделки закрываются, когда индикатор возвращается к среднему. По умолчанию используется EMAPeriod = 20.
Подробности
- Условия входа: индикатор превышает среднее на величину множителя отклонения.
- Длинные/короткие: оба направления.
- Условия выхода: индикатор возвращается к среднему.
- Стопы: да.
- Значения по умолчанию:
EMAPeriod= 20ATRPeriod= 14ATRMultiplier= 2.0mAvgPeriod= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)StopMultiplier= 2
- Фильтры:
- Категория: Breakout
- Направление: оба
- Индикаторы: Keltner
- Стопы: да
- Сложность: средняя
- Таймфрейм: краткосрочный
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Keltner Channel width breakout.
/// Enters when the channel width exceeds its average by a standard deviation multiplier,
/// in the direction of the close relative to the channel middle (EMA).
/// Exits when the width falls back below its average or the ATR stop is hit.
/// </summary>
public class KeltnerWidthBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _avgPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stopMultiplier;
private SimpleMovingAverage _widthAverage;
private StandardDeviation _widthStdDev;
private decimal _stopPrice;
/// <summary>
/// EMA period for the channel middle line.
/// </summary>
public int EMAPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// ATR period for the channel bands.
/// </summary>
public int ATRPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for the channel bands.
/// </summary>
public decimal ATRMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Period for the width average and standard deviation.
/// </summary>
public int AvgPeriod
{
get => _avgPeriod.Value;
set => _avgPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for the width breakout.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss distance in ATR multiples.
/// </summary>
public int StopMultiplier
{
get => _stopMultiplier.Value;
set => _stopMultiplier.Value = value;
}
/// <summary>
/// Initialize <see cref="KeltnerWidthBreakoutStrategy"/>.
/// </summary>
public KeltnerWidthBreakoutStrategy()
{
_emaPeriod = Param(nameof(EMAPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period of EMA for Keltner Channel", "Indicators");
_atrPeriod = Param(nameof(ATRPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of ATR for Keltner Channel", "Indicators");
_atrMultiplier = Param(nameof(ATRMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Multiplier for ATR in Keltner Channel", "Indicators");
_avgPeriod = Param(nameof(AvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Period for width average and deviation", "Strategy");
_multiplier = Param(nameof(Multiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Standard deviation multiplier for breakout", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopMultiplier = Param(nameof(StopMultiplier), 2)
.SetNotNegative()
.SetDisplay("Stop Multiplier", "Stop-loss distance in ATR multiples", "Risk Management");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_widthAverage = null;
_widthStdDev = null;
_stopPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EMAPeriod };
var atr = new AverageTrueRange { Length = ATRPeriod };
_widthAverage = new SimpleMovingAverage { Length = AvgPeriod };
_widthStdDev = new StandardDeviation { Length = AvgPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
// Width of the channel: (EMA + k*ATR) - (EMA - k*ATR).
var width = 2m * ATRMultiplier * atrValue;
var avgWidth = _widthAverage.Process(width, candle.ServerTime, true).ToDecimal();
var stdWidth = _widthStdDev.Process(width, candle.ServerTime, true).ToDecimal();
if (!_widthAverage.IsFormed || !_widthStdDev.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (CheckStop(candle))
return;
var close = candle.ClosePrice;
var stopDistance = StopMultiplier * atrValue;
if (width > avgWidth + Multiplier * stdWidth)
{
if (close > emaValue && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = StopMultiplier > 0 ? close - stopDistance : 0m;
return;
}
if (close < emaValue && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = StopMultiplier > 0 ? close + stopDistance : 0m;
return;
}
}
if (Position != 0 && width < avgWidth)
ExitPosition();
}
private bool CheckStop(ICandleMessage candle)
{
if (_stopPrice == 0m)
return false;
if (Position > 0 && candle.LowPrice <= _stopPrice)
{
ExitPosition();
return true;
}
if (Position < 0 && candle.HighPrice >= _stopPrice)
{
ExitPosition();
return true;
}
return false;
}
private void ExitPosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_stopPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class keltner_width_breakout_strategy(Strategy):
"""
Keltner Channel width breakout.
Enters when the channel width exceeds its average by a standard deviation multiplier,
in the direction of the close relative to the channel middle (EMA).
Exits when the width falls back below its average or the ATR stop is hit.
"""
def __init__(self):
super(keltner_width_breakout_strategy, self).__init__()
self._ema_period = self.Param("EMAPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("EMA Period", "Period of EMA for Keltner Channel", "Indicators")
self._atr_period = self.Param("ATRPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period of ATR for Keltner Channel", "Indicators")
self._atr_multiplier = self.Param("ATRMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Multiplier", "Multiplier for ATR in Keltner Channel", "Indicators")
self._avg_period = self.Param("AvgPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Average Period", "Period for width average and deviation", "Strategy")
self._multiplier = self.Param("Multiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Multiplier", "Standard deviation multiplier for breakout", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_multiplier = self.Param("StopMultiplier", 2) \
.SetNotNegative() \
.SetDisplay("Stop Multiplier", "Stop-loss distance in ATR multiples", "Risk Management")
self._width_average = None
self._width_std_dev = None
self._stop_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(keltner_width_breakout_strategy, self).OnReseted()
self._width_average = None
self._width_std_dev = None
self._stop_price = 0.0
def OnStarted2(self, time):
super(keltner_width_breakout_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
self._width_average = SimpleMovingAverage()
self._width_average.Length = self._avg_period.Value
self._width_std_dev = StandardDeviation()
self._width_std_dev.Length = self._avg_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
ema_value = float(ema_value)
atr_value = float(atr_value)
# Width of the channel: (EMA + k*ATR) - (EMA - k*ATR).
width = 2.0 * float(self._atr_multiplier.Value) * atr_value
avg_width = float(process_float(self._width_average, width, candle.ServerTime, True))
std_width = float(process_float(self._width_std_dev, width, candle.ServerTime, True))
if not self._width_average.IsFormed or not self._width_std_dev.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._check_stop(candle):
return
close = float(candle.ClosePrice)
stop_mult = self._stop_multiplier.Value
stop_distance = stop_mult * atr_value
if width > avg_width + float(self._multiplier.Value) * std_width:
if close > ema_value and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_distance if stop_mult > 0 else 0.0
return
if close < ema_value and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_distance if stop_mult > 0 else 0.0
return
if self.Position != 0 and width < avg_width:
self._exit_position()
def _check_stop(self, candle):
if self._stop_price == 0.0:
return False
if self.Position > 0 and float(candle.LowPrice) <= self._stop_price:
self._exit_position()
return True
if self.Position < 0 and float(candle.HighPrice) >= self._stop_price:
self._exit_position()
return True
return False
def _exit_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
self._stop_price = 0.0
def CreateClone(self):
return keltner_width_breakout_strategy()