The Auto KD Crossover strategy replicates the original MQL5 autoKD_EA example.
It uses the StochasticOscillator indicator to generate buy and sell signals based on crossovers of the %K and %D lines.
The base calculation uses the RSV formula:
RSV = (Close - LowestLow) / (HighestHigh - LowestLow) * 100
where the highest high and lowest low are computed over KDPeriod bars.
The %K line is a moving average of RSV with length KPeriod; %D is a moving average of %K with length DPeriod.
Parameters
Name
Description
Default
KDPeriod
Number of bars for the RSV base period.
30
KPeriod
Smoothing period for the %K line.
3
DPeriod
Smoothing period for the %D line.
6
CandleType
Type and timeframe of candles used for calculations.
5 minute time frame
Volume
Order volume inherited from Strategy.
Strategy.Volume
All parameters are available for optimization.
Trading Logic
Subscribe to the selected candle series and compute the Stochastic oscillator.
When the previous value of %K was below %D and the current %K crosses above %D, a long position is opened.
When the previous value of %K was above %D and the current %K crosses below %D, a short position is opened.
The strategy maintains only one position at a time. Crosses in the opposite direction close the position and open the opposite side.
StartProtection() enables default loss/profit protection mechanisms provided by StockSharp.
Visualization
The strategy automatically displays candles, the Stochastic indicator, and executed trades on the chart.
Notes
Works on any instrument and timeframe.
Parameters should be adapted to the volatility of the selected market.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on %K and %D crossover from the Stochastic oscillator.
/// </summary>
public class AutoKdStrategy : Strategy
{
private readonly StrategyParam<int> _kdPeriod;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevK;
private decimal? _prevD;
/// <summary>
/// Lookback period for RSV calculation.
/// </summary>
public int KdPeriod
{
get => _kdPeriod.Value;
set => _kdPeriod.Value = value;
}
/// <summary>
/// Smoothing period for %K.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// Smoothing period for %D.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="AutoKdStrategy"/>.
/// </summary>
public AutoKdStrategy()
{
_kdPeriod = Param(nameof(KdPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("KD Period", "Base period for RSV", "Parameters")
.SetOptimize(10, 60, 5);
_kPeriod = Param(nameof(KPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("K Period", "%K smoothing", "Parameters")
.SetOptimize(1, 10, 1);
_dPeriod = Param(nameof(DPeriod), 6)
.SetGreaterThanZero()
.SetDisplay("D Period", "%D smoothing", "Parameters")
.SetOptimize(1, 10, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevK = null;
_prevD = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stochastic = new StochasticOscillator();
stochastic.K.Length = KdPeriod;
stochastic.D.Length = DPeriod;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal k || stoch.D is not decimal d)
return;
if (_prevK is decimal prevK && _prevD is decimal prevD)
{
if (prevK < prevD && k > d && Math.Min(prevK, k) < 30m && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (prevK > prevD && k < d && Math.Max(prevK, k) > 70m && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
_prevK = k;
_prevD = d;
}
}
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 StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class auto_kd_strategy(Strategy):
def __init__(self):
super(auto_kd_strategy, self).__init__()
self._kd_period = self.Param("KdPeriod", 30) \
.SetGreaterThanZero() \
.SetDisplay("KD Period", "Base period for RSV", "Parameters")
self._k_period = self.Param("KPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("K Period", "%K smoothing", "Parameters")
self._d_period = self.Param("DPeriod", 6) \
.SetGreaterThanZero() \
.SetDisplay("D Period", "%D smoothing", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for analysis", "General")
self._prev_k = None
self._prev_d = None
@property
def kd_period(self):
return self._kd_period.Value
@property
def k_period(self):
return self._k_period.Value
@property
def d_period(self):
return self._d_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(auto_kd_strategy, self).OnReseted()
self._prev_k = None
self._prev_d = None
def OnStarted2(self, time):
super(auto_kd_strategy, self).OnStarted2(time)
stochastic = StochasticOscillator()
stochastic.K.Length = self.kd_period
stochastic.D.Length = self.d_period
sub = self.SubscribeCandles(self.candle_type)
sub.BindEx(stochastic, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, stochastic)
self.DrawOwnTrades(area)
def process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
k = stoch_value.K
d = stoch_value.D
if k is None or d is None:
return
k = float(k)
d = float(d)
if self._prev_k is not None and self._prev_d is not None:
if self._prev_k < self._prev_d and k > d and min(self._prev_k, k) < 30.0 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif self._prev_k > self._prev_d and k < d and max(self._prev_k, k) > 70.0 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._prev_k = k
self._prev_d = d
def CreateClone(self):
return auto_kd_strategy()