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= 3StochOversold= 20mStochOverbought= 80mStopLossAtr= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() Der Kanal ist der EMA plus und minus KeltnerMultiplier ATR. StochK in den Regeln ist %K: die Stochastik über StochPeriod Kerzen, geglättet über StochK Kerzen; %D spielt keine Rolle und hat daher keine Einstellung. Ein Einstieg gegen eine offene Position dreht sie. Der Stop wird auf Kerzenschlüssen geprüft.
- 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>
/// Keltner Stochastic strategy.
/// The Keltner Channel is the EmaPeriod EMA plus and minus KeltnerMultiplier times the AtrPeriod ATR, and %K is the stochastic over
/// StochPeriod candles smoothed over StochK candles. A close below the lower band with %K below StochOversold goes long, a close above
/// the upper band with %K above StochOverbought goes short, reversing an opposite position. A long closes above the EMA and a short below it.
/// The stop lies StopLossAtr ATR from the entry close and is checked on candle closes.
/// </summary>
public class KeltnerStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _keltnerMultiplier;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<decimal> _stopLossAtr;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
/// <summary>
/// Period of the channel EMA.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Period of the channel and stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the channel width.
/// </summary>
public decimal KeltnerMultiplier
{
get => _keltnerMultiplier.Value;
set => _keltnerMultiplier.Value = value;
}
/// <summary>
/// Lookback period of the raw stochastic.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Smoothing period of %K.
/// </summary>
public int StochK
{
get => _stochK.Value;
set => _stochK.Value = value;
}
/// <summary>
/// %K level for longs.
/// </summary>
public decimal StochOversold
{
get => _stochOversold.Value;
set => _stochOversold.Value = value;
}
/// <summary>
/// %K level for shorts.
/// </summary>
public decimal StochOverbought
{
get => _stochOverbought.Value;
set => _stochOverbought.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal StopLossAtr
{
get => _stopLossAtr.Value;
set => _stopLossAtr.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public KeltnerStochasticStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period of the channel EMA", "Keltner");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the channel and stop ATR", "Keltner");
_keltnerMultiplier = Param(nameof(KeltnerMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Keltner Multiplier", "ATR multiplier of the channel width", "Keltner");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Lookback period of the raw stochastic", "Stochastic");
_stochK = Param(nameof(StochK), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "Smoothing period of %K", "Stochastic");
_stochOversold = Param(nameof(StochOversold), 20m)
.SetDisplay("Stochastic Oversold", "%K level for longs", "Stochastic");
_stochOverbought = Param(nameof(StochOverbought), 80m)
.SetDisplay("Stochastic Overbought", "%K level for shorts", "Stochastic");
_stopLossAtr = Param(nameof(StopLossAtr), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = default;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
// The D line of the core oscillator is the smoothed %K.
var stochastic = new StochasticOscillator
{
K = { Length = StochPeriod },
D = { Length = StochK },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, atr, stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, stochastic);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue, IIndicatorValue atrValue, IIndicatorValue stochasticValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!emaValue.IsFormed || !atrValue.IsFormed || !stochasticValue.IsFormed)
return;
if (stochasticValue is not IStochasticOscillatorValue { D: decimal k })
return;
var middle = emaValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var upper = middle + KeltnerMultiplier * atr;
var lower = middle - KeltnerMultiplier * atr;
var close = candle.ClosePrice;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (close < lower && k < StochOversold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - StopLossAtr * atr;
}
else if (close > upper && k > StochOverbought && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + StopLossAtr * atr;
}
else if (Position > 0 && (close > middle || (StopLossAtr > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close < middle || (StopLossAtr > 0 && close >= _stopPrice)))
{
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class keltner_stochastic_strategy(Strategy):
"""
Keltner Stochastic strategy.
The Keltner Channel is the EmaPeriod EMA plus and minus KeltnerMultiplier times the AtrPeriod ATR, and %K is the stochastic over
StochPeriod candles smoothed over StochK candles. A close below the lower band with %K below StochOversold goes long, a close above
the upper band with %K above StochOverbought goes short, reversing an opposite position. A long closes above the EMA and a short below it.
The stop lies StopLossAtr ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(keltner_stochastic_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "Period of the channel EMA", "Keltner")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the channel and stop ATR", "Keltner")
self._keltner_multiplier = self.Param("KeltnerMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Keltner Multiplier", "ATR multiplier of the channel width", "Keltner")
self._stoch_period = self.Param("StochPeriod", 14).SetGreaterThanZero().SetDisplay("Stochastic Period", "Lookback period of the raw stochastic", "Stochastic")
self._stoch_k = self.Param("StochK", 3).SetGreaterThanZero().SetDisplay("Stochastic %K", "Smoothing period of %K", "Stochastic")
self._stoch_oversold = self.Param("StochOversold", 20.0).SetDisplay("Stochastic Oversold", "%K level for longs", "Stochastic")
self._stoch_overbought = self.Param("StochOverbought", 80.0).SetDisplay("Stochastic Overbought", "%K level for shorts", "Stochastic")
self._stop_loss_atr = self.Param("StopLossAtr", 2.0).SetNotNegative().SetDisplay("Stop Loss ATR", "Stop distance from the entry in ATRs", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._stop_price = Decimal(0)
def OnReseted(self):
super(keltner_stochastic_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(keltner_stochastic_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
# The D line of the core oscillator is the smoothed %K.
stochastic = StochasticOscillator()
stochastic.K.Length = self._stoch_period.Value
stochastic.D.Length = self._stoch_k.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, atr, stochastic, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, stochastic)
def _process_candle(self, candle, ema_value, atr_value, stochastic_value):
if candle.State != CandleStates.Finished:
return
if not ema_value.IsFormed or not atr_value.IsFormed or not stochastic_value.IsFormed:
return
if stochastic_value.D is None:
return
k = stochastic_value.D
middle = ema_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
multiplier = Decimal(self._keltner_multiplier.Value)
upper = middle + multiplier * atr
lower = middle - multiplier * atr
close = candle.ClosePrice
if not self.IsFormedAndOnlineAndAllowTrading():
return
stop_atr = Decimal(self._stop_loss_atr.Value)
if close < lower and k < Decimal(self._stoch_oversold.Value) and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif close > upper and k > Decimal(self._stoch_overbought.Value) and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_atr * atr
elif self.Position > 0 and (close > middle or (stop_atr > 0 and close <= self._stop_price)):
self.SellMarket(self.Position)
elif self.Position < 0 and (close < middle or (stop_atr > 0 and close >= self._stop_price)):
self.BuyMarket(-self.Position)
def CreateClone(self):
return keltner_stochastic_strategy()