This strategy is a StockSharp port of the original MQL expert Exp_Stochastic_Histogram.
It uses the Stochastic oscillator to produce contrarian trading signals in two modes:
Levels – a signal appears when %K exits the overbought or oversold areas defined by HighLevel and LowLevel.
Cross – a signal appears when %K crosses the %D line. The trade is opened in the opposite direction of the crossover.
Whenever a new signal is received, the strategy closes an existing position and opens a new one in the required direction.
Parameters
KPeriod – main %K period.
DPeriod – %D smoothing period.
Slowing – additional smoothing of %K.
HighLevel – upper threshold for the Levels mode.
LowLevel – lower threshold for the Levels mode.
Mode – either Levels or Cross.
CandleType – candle timeframe used for calculations.
How it works
For every finished candle the Stochastic oscillator is updated and evaluated. In Levels mode a long trade is opened when %K returns below the high level and a short trade when %K rises above the low level. In Cross mode a long trade is opened on downward crossovers of %K below %D, while upward crossovers trigger short trades. The strategy always has at most one open position.
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>
/// Stochastic-based strategy with crossover mode.
/// Generates contrarian signals when K crosses D.
/// </summary>
public class StochasticHistogramStrategy : Strategy
{
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevK;
private decimal? _prevD;
public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public StochasticHistogramStrategy()
{
_kPeriod = Param(nameof(KPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("K Period", "Stochastic %K period", "Stochastic");
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("D Period", "Stochastic %D period", "Stochastic");
_highLevel = Param(nameof(HighLevel), 80m)
.SetDisplay("High Level", "Upper threshold", "Stochastic");
_lowLevel = Param(nameof(LowLevel), 20m)
.SetDisplay("Low Level", "Lower threshold", "Stochastic");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for calculation", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevK = null;
_prevD = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stoch = new StochasticOscillator();
stoch.K.Length = KPeriod;
stoch.D.Length = DPeriod;
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;
var sv = stochValue as IStochasticOscillatorValue;
if (sv?.K is not decimal k || sv?.D is not decimal d)
return;
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevK = k;
_prevD = d;
return;
}
if (_prevK is decimal pk && _prevD is decimal pd)
{
// K crosses above D in oversold zone - buy
if (pk <= pd && k > d && k < LowLevel && Position <= 0)
BuyMarket();
// K crosses below D in overbought zone - sell
else if (pk >= pd && k < d && k > HighLevel && Position >= 0)
SellMarket();
}
_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 stochastic_histogram_strategy(Strategy):
def __init__(self):
super(stochastic_histogram_strategy, self).__init__()
self._k_period = self.Param("KPeriod", 5) \
.SetDisplay("K Period", "Stochastic %K period", "Stochastic")
self._d_period = self.Param("DPeriod", 3) \
.SetDisplay("D Period", "Stochastic %D period", "Stochastic")
self._high_level = self.Param("HighLevel", 80.0) \
.SetDisplay("High Level", "Upper threshold", "Stochastic")
self._low_level = self.Param("LowLevel", 20.0) \
.SetDisplay("Low Level", "Lower threshold", "Stochastic")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for calculation", "General")
self._prev_k = None
self._prev_d = None
@property
def k_period(self):
return self._k_period.Value
@property
def d_period(self):
return self._d_period.Value
@property
def high_level(self):
return self._high_level.Value
@property
def low_level(self):
return self._low_level.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stochastic_histogram_strategy, self).OnReseted()
self._prev_k = None
self._prev_d = None
def OnStarted2(self, time):
super(stochastic_histogram_strategy, self).OnStarted2(time)
stoch = StochasticOscillator()
stoch.K.Length = self.k_period
stoch.D.Length = self.d_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(stoch, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, stoch)
self.DrawOwnTrades(area)
def process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
if not stoch_value.IsFormed:
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:
pk = self._prev_k
pd = self._prev_d
if pk <= pd and k > d and k < float(self.low_level) and self.Position <= 0:
self.BuyMarket()
elif pk >= pd and k < d and k > float(self.high_level) and self.Position >= 0:
self.SellMarket()
self._prev_k = k
self._prev_d = d
def CreateClone(self):
return stochastic_histogram_strategy()