Strategie Keltner Stochastic
Strategie, die Keltner Channels und den Stochastic Oszillator kombiniert. Einstieg in Positionen, wenn der Preis die Keltner Channel-Grenzen erreicht und Stochastic überkaufte/überverkaufte Bedingungen bestätigt.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 163%. Sie funktioniert am besten im Aktienmarkt.
Dieses Setup zielt darauf ab, Umkehrungen nahe den Keltner-Bändern zu erfassen, während der Oszillator Impulswechsel bestätigt. Signale können in beide Richtungen ausgelöst werden, wenn der Preis gegen einen Kanal drückt.
Kurzfristige Trader, die schnelle Umkehrungen suchen, können es nützlich finden. Das Risiko wird durch einen ATR-basierten Stop-Abstand begrenzt.
Details
- Einstiegskriterien:
- Long:
Close < LowerBand && StochK < StochOversold - Short:
Close > UpperBand && StochK > StochOverbought
- Long:
- Long/Short: Beide
- Ausstiegskriterien:
- Long:
Close > EMA - Short:
Close < EMA
- Long:
- Stops:
StopLossAtrATR vom Einstieg - Standardwerte:
EmaPeriod= 20AtrPeriod= 14KeltnerMultiplier= 2.0mStochPeriod= 14StochK= 3StochD= 3StochOversold= 20mStochOverbought= 80mStopLossAtr= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filter:
- Kategorie: Mean Reversion
- Richtung: Beide
- Indikatoren: Keltner Channel, Stochastic Oscillator
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Mittelfristig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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>
/// Strategy that combines Keltner Channels (EMA + ATR) and manual Stochastic %K.
/// Enters when price reaches Keltner bands and Stochastic confirms oversold/overbought.
/// </summary>
public class KeltnerStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _keltnerMultiplier;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<int> _cooldownBars;
private decimal _atrValue;
private int _cooldown;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private const int StochPeriod = 14;
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// EMA period for Keltner Channel.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Keltner Channel multiplier.
/// </summary>
public decimal KeltnerMultiplier
{
get => _keltnerMultiplier.Value;
set => _keltnerMultiplier.Value = value;
}
/// <summary>
/// Stochastic oversold level.
/// </summary>
public decimal StochOversold
{
get => _stochOversold.Value;
set => _stochOversold.Value = value;
}
/// <summary>
/// Stochastic overbought level.
/// </summary>
public decimal StochOverbought
{
get => _stochOverbought.Value;
set => _stochOverbought.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy constructor.
/// </summary>
public KeltnerStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetRange(10, 30)
.SetDisplay("EMA Period", "Period of the EMA for Keltner Channel", "Indicators");
_keltnerMultiplier = Param(nameof(KeltnerMultiplier), 2.0m)
.SetDisplay("Keltner Multiplier", "Multiplier for ATR in Keltner Channel", "Indicators");
_stochOversold = Param(nameof(StochOversold), 20m)
.SetDisplay("Stochastic Oversold", "Level considered oversold", "Indicators");
_stochOverbought = Param(nameof(StochOverbought), 80m)
.SetDisplay("Stochastic Overbought", "Level considered overbought", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atrValue = 0;
_cooldown = 0;
_highs.Clear();
_lows.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = 14 };
var subscription = SubscribeCandles(CandleType);
// Bind ATR to capture value
subscription.BindEx(atr, OnAtr);
// Bind EMA for main logic
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void OnAtr(ICandleMessage candle, IIndicatorValue atrValue)
{
if (atrValue.IsFormed)
_atrValue = atrValue.ToDecimal();
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_atrValue <= 0)
return;
// Track highs/lows for stochastic
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
var maxBuf = StochPeriod * 2;
if (_highs.Count > maxBuf)
{
_highs.RemoveRange(0, _highs.Count - maxBuf);
_lows.RemoveRange(0, _lows.Count - maxBuf);
}
if (_highs.Count < StochPeriod)
return;
// Manual Stochastic %K
var start = _highs.Count - StochPeriod;
var highestHigh = decimal.MinValue;
var lowestLow = decimal.MaxValue;
for (var i = start; i < _highs.Count; i++)
{
if (_highs[i] > highestHigh) highestHigh = _highs[i];
if (_lows[i] < lowestLow) lowestLow = _lows[i];
}
var diff = highestHigh - lowestLow;
if (diff == 0) return;
var stochK = 100m * (candle.ClosePrice - lowestLow) / diff;
// Keltner Channel
var upperBand = emaValue + (KeltnerMultiplier * _atrValue);
var lowerBand = emaValue - (KeltnerMultiplier * _atrValue);
var close = candle.ClosePrice;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Long: price below lower Keltner + Stochastic oversold
if (close < lowerBand && stochK < StochOversold && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Short: price above upper Keltner + Stochastic overbought
else if (close > upperBand && stochK > StochOverbought && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price above EMA
if (Position > 0 && close > emaValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price below EMA
else if (Position < 0 && close < emaValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class keltner_stochastic_strategy(Strategy):
"""
Keltner Channel + manual Stochastic %K. Mean reversion at band extremes.
"""
def __init__(self):
super(keltner_stochastic_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetDisplay("EMA Period", "Keltner EMA period", "Indicators")
self._keltner_mult = self.Param("KeltnerMultiplier", 2.0).SetDisplay("Keltner Mult", "ATR multiplier", "Indicators")
self._stoch_oversold = self.Param("StochOversold", 20.0).SetDisplay("Stoch Oversold", "Oversold level", "Indicators")
self._stoch_overbought = self.Param("StochOverbought", 80.0).SetDisplay("Stoch Overbought", "Overbought level", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 100).SetDisplay("Cooldown", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._atr_value = 0.0
self._cooldown = 0
self._highs = []
self._lows = []
self._stoch_period = 14
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(keltner_stochastic_strategy, self).OnReseted()
self._atr_value = 0.0
self._cooldown = 0
self._highs = []
self._lows = []
def OnStarted2(self, time):
super(keltner_stochastic_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, self._on_atr)
subscription.Bind(ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _on_atr(self, candle, atr_val):
if atr_val.IsFinal:
self._atr_value = float(atr_val.Value)
def _process_candle(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
if self._atr_value <= 0:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._highs.append(high)
self._lows.append(low)
max_buf = self._stoch_period * 2
if len(self._highs) > max_buf:
self._highs = self._highs[-max_buf:]
self._lows = self._lows[-max_buf:]
if len(self._highs) < self._stoch_period:
return
recent_h = self._highs[-self._stoch_period:]
recent_l = self._lows[-self._stoch_period:]
hh = max(recent_h)
ll = min(recent_l)
diff = hh - ll
if diff == 0:
return
stoch_k = 100.0 * (close - ll) / diff
ema = float(ema_val)
upper = ema + self._keltner_mult.Value * self._atr_value
lower = ema - self._keltner_mult.Value * self._atr_value
if self._cooldown > 0:
self._cooldown -= 1
return
if close < lower and stoch_k < self._stoch_oversold.Value and self.Position == 0:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
elif close > upper and stoch_k > self._stoch_overbought.Value and self.Position == 0:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
if self.Position > 0 and close > ema:
self.SellMarket()
self._cooldown = self._cooldown_bars.Value
elif self.Position < 0 and close < ema:
self.BuyMarket()
self._cooldown = self._cooldown_bars.Value
def CreateClone(self):
return keltner_stochastic_strategy()