Strategie Stochastic Keltner
Diese Strategie verwendet Stochastic Keltner Indikatoren zur Signalgenerierung. Ein Long-Einstieg erfolgt, wenn Stoch %K < StochOversold && Price < Keltner lower band (überverkauft am unteren Band). Ein Short-Einstieg erfolgt, wenn Stoch %K > StochOverbought && Price > Keltner upper band (überkauft am oberen Band). Sie eignet sich für Trader, die Chancen in gemischten Märkten suchen.
Tests zeigen eine durchschnittliche Jahresrendite von etwa 61%. Sie funktioniert am besten auf dem Kryptomarkt.
Details
- Einstiegskriterien:
- Long: Stoch %K < StochOversold && Price < Keltner lower band (oversold at lower band)
- Short: Stoch %K > StochOverbought && Price > Keltner upper band (overbought at upper band)
- Long/Short: Beide Seiten.
- Ausstiegskriterien:
- Long: Long-Position schließen, wenn der Preis zum mittleren Band zurückkehrt
- Short: Short-Position schließen, wenn der Preis zum mittleren Band zurückkehrt
- Stops: Ja.
- Standardwerte:
StochPeriod= 14StochK= 3StochOversold= 20StochOverbought= 80EmaPeriod= 20KeltnerMultiplier= 2AtrPeriod= 14AtrMultiplier= 2 20 und 80 sind die Standardwerte der %K-Schwellen, die die Regeln nennen. Die Bänder sind der EMA über EmaPeriod plus und minus KeltnerMultiplier mal der ATR über AtrPeriod, das mittlere Band ist der EMA selbst. Der Stop liegt AtrMultiplier mal dieselbe ATR vom Einstiegsschluss entfernt und wird bei Kerzenschluss geprüft; 0 schaltet ihn ab. 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 Einstiegssignal gegen eine offene Position dreht sie.CandleType= TimeSpan.FromMinutes(5)
- Filter:
- Kategorie: Gemischt
- Richtung: Beide
- Indikatoren: Stochastic Keltner
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday
- 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>
/// Stochastic Keltner strategy.
/// The Keltner Channel is the EmaPeriod EMA plus and minus KeltnerMultiplier times the AtrPeriod ATR; %K is the stochastic over StochPeriod
/// candles smoothed over StochK candles. %K below StochOversold with a close below the lower band goes long and %K above StochOverbought
/// with a close above the upper band goes short, reversing an opposite position. A position closes once price returns to the middle band.
/// The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
/// </summary>
public class StochasticKeltnerStrategy : Strategy
{
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _keltnerMultiplier;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
/// <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>
/// Period of the channel EMA.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the channel width.
/// </summary>
public decimal KeltnerMultiplier
{
get => _keltnerMultiplier.Value;
set => _keltnerMultiplier.Value = value;
}
/// <summary>
/// Period of the channel and stop ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop distance from the entry in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public StochasticKeltnerStrategy()
{
_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");
_emaPeriod = Param(nameof(EmaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Period of the channel EMA", "Keltner");
_keltnerMultiplier = Param(nameof(KeltnerMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Keltner Multiplier", "ATR multiplier of the channel width", "Keltner");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the channel and stop ATR", "Keltner");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "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 stochastic = new StochasticOscillator
{
K = { Length = StochPeriod },
D = { Length = StochK },
};
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(stochastic, ema, atr, 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 stochasticValue, IIndicatorValue emaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!stochasticValue.IsFormed || !emaValue.IsFormed || !atrValue.IsFormed)
return;
// The smoothed %K is the moving average the core oscillator exposes as D.
if (stochasticValue is not IStochasticOscillatorValue { D: decimal k })
return;
if (!IsFormedAndOnlineAndAllowTrading())
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 (k < StochOversold && close < lower && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - AtrMultiplier * atr;
}
else if (k > StochOverbought && close > upper && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + AtrMultiplier * atr;
}
else if (Position > 0 && (close >= middle || (AtrMultiplier > 0 && close <= _stopPrice)))
{
SellMarket(Position);
}
else if (Position < 0 && (close <= middle || (AtrMultiplier > 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 StochasticOscillator, ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class stochastic_keltner_strategy(Strategy):
"""
Stochastic Keltner strategy.
The Keltner Channel is the EmaPeriod EMA plus and minus KeltnerMultiplier times the AtrPeriod ATR; %K is the stochastic over StochPeriod
candles smoothed over StochK candles. %K below StochOversold with a close below the lower band goes long and %K above StochOverbought
with a close above the upper band goes short, reversing an opposite position. A position closes once price returns to the middle band.
The stop lies AtrMultiplier ATR from the entry close and is checked on candle closes.
"""
def __init__(self):
super(stochastic_keltner_strategy, self).__init__()
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._ema_period = self.Param("EmaPeriod", 20).SetGreaterThanZero().SetDisplay("EMA Period", "Period of the channel EMA", "Keltner")
self._keltner_multiplier = self.Param("KeltnerMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Keltner Multiplier", "ATR multiplier of the channel width", "Keltner")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the channel and stop ATR", "Keltner")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Multiplier", "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(stochastic_keltner_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(stochastic_keltner_strategy, self).OnStarted2(time)
self._reset_state()
stochastic = StochasticOscillator()
stochastic.K.Length = self._stoch_period.Value
stochastic.D.Length = self._stoch_k.Value
ema = ExponentialMovingAverage()
ema.Length = self._ema_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(stochastic, 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)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, stochastic)
def _process_candle(self, candle, stochastic_value, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not stochastic_value.IsFormed or not ema_value.IsFormed or not atr_value.IsFormed:
return
# The smoothed %K is the moving average the core oscillator exposes as D.
if stochastic_value.D is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
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
stop_atr = Decimal(self._atr_multiplier.Value)
if k < Decimal(self._stoch_oversold.Value) and close < lower and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_atr * atr
elif k > Decimal(self._stoch_overbought.Value) and close > upper 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 stochastic_keltner_strategy()