自适应KDJ (MTF)
自适应KDJ策略将三个不同时间框架的KDJ振荡器值进行加权平均,并使用EMA进行平滑处理。合成振荡器的SMA用来衡量趋势强度,从而动态调整超买和超卖水平。
当J线位于自适应买入水平下方且K线向上穿越D线时,策略做多。J线高于自适应卖出水平且K线向下穿越D线时,策略做空。
详情
- 入场条件: KDJ交叉且J线低于/高于动态水平。
- 多空方向: 双向。
- 出场条件: 反向信号。
- 止损: 无。
- 默认值:
TimeFrame1= TimeSpan.FromMinutes(1)TimeFrame2= TimeSpan.FromMinutes(3)TimeFrame3= TimeSpan.FromMinutes(15)KdjLength= 9SmoothingLength= 5TrendLength= 40WeightOption= 1
- 过滤器:
- 类别: Oscillator
- 方向: 双向
- 指标: Stochastic, EMA, SMA
- 止损: 无
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 无
- 神经网络: 无
- 背离: 无
- 风险等级: 中等
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy based on KDJ (Stochastic-based) signals with smoothing.
/// Uses Stochastic K/D crossover with J-value extremes for entry/exit.
/// </summary>
public class AdaptiveKdjMtfStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _kdjLength;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<decimal> _buyLevel;
private readonly StrategyParam<decimal> _sellLevel;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevK;
private decimal _prevD;
private decimal _smoothK;
private decimal _smoothD;
private bool _hasPrev;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int KdjLength { get => _kdjLength.Value; set => _kdjLength.Value = value; }
public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
public decimal BuyLevel { get => _buyLevel.Value; set => _buyLevel.Value = value; }
public decimal SellLevel { get => _sellLevel.Value; set => _sellLevel.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public AdaptiveKdjMtfStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle type", "General");
_kdjLength = Param(nameof(KdjLength), 9)
.SetGreaterThanZero()
.SetDisplay("KDJ Length", "Base length for Stochastic", "Parameters")
.SetOptimize(3, 15, 3);
_smoothingLength = Param(nameof(SmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "EMA smoothing length", "Parameters")
.SetOptimize(3, 15, 2);
_buyLevel = Param(nameof(BuyLevel), 30m)
.SetDisplay("Buy Level", "J value threshold for buy signal", "Parameters")
.SetOptimize(15m, 40m, 5m);
_sellLevel = Param(nameof(SellLevel), 70m)
.SetDisplay("Sell Level", "J value threshold for sell signal", "Parameters")
.SetOptimize(60m, 85m, 5m);
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevK = 50m;
_prevD = 50m;
_smoothK = 50m;
_smoothD = 50m;
_hasPrev = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stoch = new StochasticOscillator();
stoch.K.Length = KdjLength;
stoch.D.Length = 3;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(stoch, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, stoch);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (stochValue.IsEmpty)
return;
var sv = (IStochasticOscillatorValue)stochValue;
if (sv.K is not decimal k || sv.D is not decimal d)
return;
// Compute J = 3K - 2D (KDJ formula)
var j = 3m * k - 2m * d;
// EMA smoothing
var alpha = 2m / (SmoothingLength + 1m);
_smoothK = alpha * k + (1m - alpha) * _smoothK;
_smoothD = alpha * d + (1m - alpha) * _smoothD;
var smoothJ = alpha * j + (1m - alpha) * j;
if (!_hasPrev)
{
_prevK = _smoothK;
_prevD = _smoothD;
_hasPrev = true;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevK = _smoothK;
_prevD = _smoothD;
return;
}
var crossUp = _prevK <= _prevD && _smoothK > _smoothD;
var crossDown = _prevK >= _prevD && _smoothK < _smoothD;
var buySignal = smoothJ < BuyLevel && crossUp;
var sellSignal = smoothJ > SellLevel && crossDown;
if (buySignal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
else if (sellSignal && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
_prevK = _smoothK;
_prevD = _smoothD;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class adaptive_kdj_mtf_strategy(Strategy):
"""Adaptive KDJ MTF Strategy."""
def __init__(self):
super(adaptive_kdj_mtf_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle type", "General")
self._kdj_length = self.Param("KdjLength", 9) \
.SetDisplay("KDJ Length", "Base length for Stochastic", "Parameters")
self._smoothing_length = self.Param("SmoothingLength", 5) \
.SetDisplay("Smoothing Length", "EMA smoothing length", "Parameters")
self._buy_level = self.Param("BuyLevel", 30.0) \
.SetDisplay("Buy Level", "J value threshold for buy signal", "Parameters")
self._sell_level = self.Param("SellLevel", 70.0) \
.SetDisplay("Sell Level", "J value threshold for sell signal", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prev_k = 50.0
self._prev_d = 50.0
self._smooth_k = 50.0
self._smooth_d = 50.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adaptive_kdj_mtf_strategy, self).OnReseted()
self._prev_k = 50.0
self._prev_d = 50.0
self._smooth_k = 50.0
self._smooth_d = 50.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adaptive_kdj_mtf_strategy, self).OnStarted2(time)
stoch = StochasticOscillator()
stoch.K.Length = int(self._kdj_length.Value)
stoch.D.Length = 3
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(stoch, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, stoch)
self.DrawOwnTrades(area)
def _on_process(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if stoch_value.IsEmpty:
return
if stoch_value.K is None or stoch_value.D is None:
return
k = float(stoch_value.K)
d = float(stoch_value.D)
j = 3.0 * k - 2.0 * d
alpha = 2.0 / (float(self._smoothing_length.Value) + 1.0)
self._smooth_k = alpha * k + (1.0 - alpha) * self._smooth_k
self._smooth_d = alpha * d + (1.0 - alpha) * self._smooth_d
smooth_j = alpha * j + (1.0 - alpha) * j
cooldown = int(self._cooldown_bars.Value)
if not self._has_prev:
self._prev_k = self._smooth_k
self._prev_d = self._smooth_d
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_k = self._smooth_k
self._prev_d = self._smooth_d
return
cross_up = self._prev_k <= self._prev_d and self._smooth_k > self._smooth_d
cross_down = self._prev_k >= self._prev_d and self._smooth_k < self._smooth_d
buy_signal = smooth_j < float(self._buy_level.Value) and cross_up
sell_signal = smooth_j > float(self._sell_level.Value) and cross_down
if buy_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif sell_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
self._prev_k = self._smooth_k
self._prev_d = self._smooth_d
def CreateClone(self):
return adaptive_kdj_mtf_strategy()