Kaufman Trend-Strategie
Die Kaufman Trend-Strategie verwendet einen Kalman-Filter, um Preis und Momentum zu schätzen. Die Trendstärke wird aus der Geschwindigkeitskomponente des Filters abgeleitet und über ein aktuelles Fenster normalisiert. Einstiege erfolgen, wenn starke Trendbedingungen mit einem Preis über oder unter dem gefilterten Wert zusammenfallen. Stops basieren auf aktuellen Swings plus ATR, und Gewinne werden schrittweise mitgenommen, wenn das Momentum nachlässt.
Details
- Einstiegskriterien: Trendstärkeschwellenwert mit Preis über/unter dem gefilterten Wert.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: gestaffelte Gewinnmitnahmen und Trendabschwächung oder Stop-Auslösung.
- Stops: Ja, Swing-Tief/Hoch minus/plus ATR.
- Standardwerte:
TakeProfit1Percent = 50TakeProfit2Percent = 25TakeProfit3Percent = 25SwingLookback = 10AtrPeriod = 14TrendStrengthEntry = 60TrendStrengthExit = 40CandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Filter:
- Kategorie: Trendfolge
- Richtung: Beide
- Indikatoren: Kalman
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Intraday (15m)
- 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>
/// Kaufman Trend strategy using Kalman filter for trend detection.
/// </summary>
public class KaufmanTrendStrategy : Strategy
{
private readonly StrategyParam<int> _trendStrengthEntry;
private readonly StrategyParam<int> _trendStrengthExit;
private readonly StrategyParam<decimal> _processNoise;
private readonly StrategyParam<decimal> _measurementNoise;
private readonly StrategyParam<int> _oscBufferLength;
private readonly StrategyParam<int> _maxEntries;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _filteredSrc;
private decimal _oscillator;
private decimal _p00 = 1m;
private decimal _p01;
private decimal _p10;
private decimal _p11 = 1m;
private decimal _oscAbsAverage;
private int _warmupCount;
private int _entriesExecuted;
private int _barsSinceSignal;
public int TrendStrengthEntry
{
get => _trendStrengthEntry.Value;
set => _trendStrengthEntry.Value = value;
}
public int TrendStrengthExit
{
get => _trendStrengthExit.Value;
set => _trendStrengthExit.Value = value;
}
public decimal ProcessNoise
{
get => _processNoise.Value;
set => _processNoise.Value = value;
}
public decimal MeasurementNoise
{
get => _measurementNoise.Value;
set => _measurementNoise.Value = value;
}
public int OscBufferLength
{
get => _oscBufferLength.Value;
set => _oscBufferLength.Value = value;
}
public int MaxEntries
{
get => _maxEntries.Value;
set => _maxEntries.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public KaufmanTrendStrategy()
{
_trendStrengthEntry = Param(nameof(TrendStrengthEntry), 80)
.SetDisplay("Trend Strength Entry", "Entry threshold.", "Trend");
_trendStrengthExit = Param(nameof(TrendStrengthExit), 20)
.SetDisplay("Trend Strength Exit", "Exit threshold.", "Trend");
_processNoise = Param(nameof(ProcessNoise), 0.01m)
.SetDisplay("Process Noise", "Kalman process noise.", "Kalman");
_measurementNoise = Param(nameof(MeasurementNoise), 500m)
.SetDisplay("Measurement Noise", "Observation noise.", "Kalman");
_oscBufferLength = Param(nameof(OscBufferLength), 10)
.SetDisplay("Oscillator Buffer", "Bars for normalization.", "Trend");
_maxEntries = Param(nameof(MaxEntries), 45)
.SetDisplay("Max Entries", "Maximum entries per run.", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 300)
.SetDisplay("Cooldown Bars", "Minimum bars between entries.", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles.", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_filteredSrc = 0m;
_oscillator = 0m;
_p00 = 1m;
_p01 = 0m;
_p10 = 0m;
_p11 = 1m;
_oscAbsAverage = 0m;
_warmupCount = 0;
_entriesExecuted = 0;
_barsSinceSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_filteredSrc = 0m;
_oscillator = 0m;
_p00 = 1m;
_p01 = 0m;
_p10 = 0m;
_p11 = 1m;
_oscAbsAverage = 0m;
_warmupCount = 0;
_entriesExecuted = 0;
_barsSinceSignal = CooldownBars;
var atr = new AverageTrueRange { Length = 14 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceSignal++;
UpdateKalman(candle.ClosePrice);
var absOsc = Math.Abs(_oscillator);
if (_warmupCount == 0)
{
_oscAbsAverage = absOsc;
}
else
{
var alpha = 2m / (OscBufferLength + 1m);
_oscAbsAverage += (absOsc - _oscAbsAverage) * alpha;
}
_warmupCount++;
var trendStrength = _oscAbsAverage > 0m ? _oscillator / _oscAbsAverage * 100m : 0m;
if (_warmupCount < OscBufferLength)
return;
var priceAboveMa = candle.ClosePrice > _filteredSrc;
var priceBelowMa = candle.ClosePrice < _filteredSrc;
var trendStrongLong = trendStrength >= TrendStrengthEntry;
var trendStrongShort = trendStrength <= -TrendStrengthEntry;
var trendWeakLong = trendStrength < TrendStrengthExit;
var trendWeakShort = trendStrength > -TrendStrengthExit;
// Exit logic
if (Position > 0 && trendWeakLong)
{
SellMarket(Math.Abs(Position));
_barsSinceSignal = 0;
}
else if (Position < 0 && trendWeakShort)
{
BuyMarket(Math.Abs(Position));
_barsSinceSignal = 0;
}
// Entry logic
if (Position == 0 && _entriesExecuted < MaxEntries && _barsSinceSignal >= CooldownBars)
{
if (trendStrongLong && priceAboveMa)
{
BuyMarket();
_entriesExecuted++;
_barsSinceSignal = 0;
}
else if (trendStrongShort && priceBelowMa)
{
SellMarket();
_entriesExecuted++;
_barsSinceSignal = 0;
}
}
}
private void UpdateKalman(decimal price)
{
if (_filteredSrc == 0m)
{
_filteredSrc = price;
return;
}
_filteredSrc += _oscillator;
var p00p = _p00 + _p01 + _p10 + _p11 + ProcessNoise;
var p01p = _p01 + _p11;
var p10p = _p10 + _p11;
var p11p = _p11 + ProcessNoise;
var s = p00p + MeasurementNoise;
if (s == 0m) return;
var k0 = p00p / s;
var k1 = p10p / s;
var innovation = price - _filteredSrc;
_filteredSrc += k0 * innovation;
_oscillator += k1 * innovation;
_p00 = (1 - k0) * p00p;
_p01 = (1 - k0) * p01p;
_p10 = p10p - k1 * p00p;
_p11 = p11p - k1 * p01p;
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class kaufman_trend_strategy(Strategy):
def __init__(self):
super(kaufman_trend_strategy, self).__init__()
self._trend_strength_entry = self.Param("TrendStrengthEntry", 80) \
.SetDisplay("Trend Strength Entry", "Entry threshold", "Trend")
self._trend_strength_exit = self.Param("TrendStrengthExit", 20) \
.SetDisplay("Trend Strength Exit", "Exit threshold", "Trend")
self._process_noise = self.Param("ProcessNoise", 0.01) \
.SetDisplay("Process Noise", "Kalman process noise", "Kalman")
self._measurement_noise = self.Param("MeasurementNoise", 500.0) \
.SetDisplay("Measurement Noise", "Observation noise", "Kalman")
self._osc_buffer_length = self.Param("OscBufferLength", 10) \
.SetDisplay("Oscillator Buffer", "Bars for normalization", "Trend")
self._max_entries = self.Param("MaxEntries", 45) \
.SetDisplay("Max Entries", "Maximum entries per run", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 300) \
.SetDisplay("Cooldown Bars", "Minimum bars between entries", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._filtered_src = 0.0
self._oscillator = 0.0
self._p00 = 1.0
self._p01 = 0.0
self._p10 = 0.0
self._p11 = 1.0
self._osc_abs_average = 0.0
self._warmup_count = 0
self._entries_executed = 0
self._bars_since_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(kaufman_trend_strategy, self).OnReseted()
self._filtered_src = 0.0
self._oscillator = 0.0
self._p00 = 1.0
self._p01 = 0.0
self._p10 = 0.0
self._p11 = 1.0
self._osc_abs_average = 0.0
self._warmup_count = 0
self._entries_executed = 0
self._bars_since_signal = 0
def OnStarted2(self, time):
super(kaufman_trend_strategy, self).OnStarted2(time)
self._entries_executed = 0
self._bars_since_signal = self._cooldown_bars.Value
atr = AverageTrueRange()
atr.Length = 14
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _update_kalman(self, price):
pn = float(self._process_noise.Value)
mn = float(self._measurement_noise.Value)
if self._filtered_src == 0.0:
self._filtered_src = price
return
self._filtered_src += self._oscillator
p00p = self._p00 + self._p01 + self._p10 + self._p11 + pn
p01p = self._p01 + self._p11
p10p = self._p10 + self._p11
p11p = self._p11 + pn
s = p00p + mn
if s == 0.0:
return
k0 = p00p / s
k1 = p10p / s
innovation = price - self._filtered_src
self._filtered_src += k0 * innovation
self._oscillator += k1 * innovation
self._p00 = (1.0 - k0) * p00p
self._p01 = (1.0 - k0) * p01p
self._p10 = p10p - k1 * p00p
self._p11 = p11p - k1 * p01p
def OnProcess(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
self._bars_since_signal += 1
close = float(candle.ClosePrice)
self._update_kalman(close)
abs_osc = abs(self._oscillator)
osc_buf = self._osc_buffer_length.Value
if self._warmup_count == 0:
self._osc_abs_average = abs_osc
else:
alpha = 2.0 / (osc_buf + 1.0)
self._osc_abs_average += (abs_osc - self._osc_abs_average) * alpha
self._warmup_count += 1
if self._osc_abs_average > 0.0:
trend_strength = self._oscillator / self._osc_abs_average * 100.0
else:
trend_strength = 0.0
if self._warmup_count < osc_buf:
return
entry_th = float(self._trend_strength_entry.Value)
exit_th = float(self._trend_strength_exit.Value)
price_above = close > self._filtered_src
price_below = close < self._filtered_src
strong_long = trend_strength >= entry_th
strong_short = trend_strength <= -entry_th
weak_long = trend_strength < exit_th
weak_short = trend_strength > -exit_th
if self.Position > 0 and weak_long:
self.SellMarket()
self._bars_since_signal = 0
elif self.Position < 0 and weak_short:
self.BuyMarket()
self._bars_since_signal = 0
if self.Position == 0 and self._entries_executed < self._max_entries.Value and self._bars_since_signal >= self._cooldown_bars.Value:
if strong_long and price_above:
self.BuyMarket()
self._entries_executed += 1
self._bars_since_signal = 0
elif strong_short and price_below:
self.SellMarket()
self._entries_executed += 1
self._bars_since_signal = 0
def CreateClone(self):
return kaufman_trend_strategy()