Kaufman Trend 策略
Kaufman Trend Strategy 使用卡尔曼滤波器估计价格及其速度。趋势强度由速度计算,并在最近窗口内归一化。当趋势强度达到阈值且价格位于滤波值之上或之下时触发进场。止损基于最近摆动加减 ATR,利润在动能减弱时分批了结。
细节
- 入场条件:趋势强度阈值与价格相对滤波值的位置。
- 多/空:双向。
- 出场条件:分批止盈与趋势减弱或触发止损。
- 止损:是,最近低/高点 ± ATR。
- 默认值:
TakeProfit1Percent = 50TakeProfit2Percent = 25TakeProfit3Percent = 25SwingLookback = 10AtrPeriod = 14TrendStrengthEntry = 60TrendStrengthExit = 40CandleType = TimeSpan.FromMinutes(15).TimeFrame()
- 过滤器:
- 类别:趋势跟随
- 方向:双向
- 指标:卡尔曼
- 止损:是
- 复杂度:中等
- 时间框架:日内 (15m)
- 季节性:否
- 神经网络:否
- 背离:否
- 风险水平:中等
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()