Stochastic Dynamic Zones
The Stochastic Dynamic Zones strategy is built around Stochastic Oscillator with Dynamic Overbought/Oversold Zones.
Testing indicates an average annual return of about 52%. It performs best in the crypto market.
Signals trigger when Stochastic confirms trend changes on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like StochPeriod, StochKPeriod. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
StochPeriod = 14StochKPeriod = 3StochDPeriod = 3LookbackPeriod = 20StandardDeviationFactor = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Stochastic
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Stochastic Oscillator with dynamic overbought and oversold zones.
/// The zones are the average of %K over <see cref="LookbackPeriod"/> bars plus/minus
/// <see cref="StandardDeviationFactor"/> standard deviations. Buys when %K crosses above %D inside the oversold zone
/// and sells when %K crosses below %D inside the overbought zone; the opposite signal reverses the position.
/// </summary>
public class StochasticWithDynamicZonesStrategy : Strategy
{
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _stochKPeriod;
private readonly StrategyParam<int> _stochDPeriod;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _standardDeviationFactor;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _kSmoothing;
private SimpleMovingAverage _dLine;
private SimpleMovingAverage _zoneAverage;
private StandardDeviation _zoneStdDev;
private decimal? _prevK;
private decimal? _prevD;
/// <summary>
/// Stochastic lookback period.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Smoothing period of %K.
/// </summary>
public int StochKPeriod
{
get => _stochKPeriod.Value;
set => _stochKPeriod.Value = value;
}
/// <summary>
/// Period of %D.
/// </summary>
public int StochDPeriod
{
get => _stochDPeriod.Value;
set => _stochDPeriod.Value = value;
}
/// <summary>
/// Lookback period for the dynamic zones.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Standard deviation factor for the dynamic zones.
/// </summary>
public decimal StandardDeviationFactor
{
get => _standardDeviationFactor.Value;
set => _standardDeviationFactor.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="StochasticWithDynamicZonesStrategy"/>.
/// </summary>
public StochasticWithDynamicZonesStrategy()
{
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Stochastic lookback period", "Indicators");
_stochKPeriod = Param(nameof(StochKPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Stoch %K Period", "Smoothing period of %K", "Indicators");
_stochDPeriod = Param(nameof(StochDPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Stoch %D Period", "Period of %D", "Indicators");
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Period for dynamic zones", "Indicators");
_standardDeviationFactor = Param(nameof(StandardDeviationFactor), 2.0m)
.SetGreaterThanZero()
.SetDisplay("StdDev Factor", "Standard deviation factor for dynamic zones", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_kSmoothing = null;
_dLine = null;
_zoneAverage = null;
_zoneStdDev = null;
_prevK = null;
_prevD = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var rawK = new StochasticK { Length = StochPeriod };
_kSmoothing = new SimpleMovingAverage { Length = StochKPeriod };
_dLine = new SimpleMovingAverage { Length = StochDPeriod };
_zoneAverage = new SimpleMovingAverage { Length = LookbackPeriod };
_zoneStdDev = new StandardDeviation { Length = LookbackPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(rawK, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rawKValue)
{
if (candle.State != CandleStates.Finished)
return;
var k = _kSmoothing.Process(rawKValue, candle.ServerTime, true).ToDecimal();
if (!_kSmoothing.IsFormed)
return;
var d = _dLine.Process(k, candle.ServerTime, true).ToDecimal();
var average = _zoneAverage.Process(k, candle.ServerTime, true).ToDecimal();
var stdDev = _zoneStdDev.Process(k, candle.ServerTime, true).ToDecimal();
var prevK = _prevK;
var prevD = _prevD;
_prevK = k;
_prevD = d;
if (!_dLine.IsFormed || !_zoneAverage.IsFormed || !_zoneStdDev.IsFormed)
return;
if (prevK is not decimal pk || prevD is not decimal pd)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var oversold = average - StandardDeviationFactor * stdDev;
var overbought = average + StandardDeviationFactor * stdDev;
var crossUp = pk <= pd && k > d;
var crossDown = pk >= pd && k < d;
if (crossUp && k < oversold && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossDown && k > overbought && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
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 StochasticK, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class stochastic_with_dynamic_zones_strategy(Strategy):
"""
Stochastic Oscillator with dynamic overbought and oversold zones.
The zones are the average of %K over LookbackPeriod bars plus/minus StandardDeviationFactor
standard deviations. Buys when %K crosses above %D inside the oversold zone and sells when %K
crosses below %D inside the overbought zone; the opposite signal reverses the position.
"""
def __init__(self):
super(stochastic_with_dynamic_zones_strategy, self).__init__()
self._stoch_period = self.Param("StochPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Stochastic Period", "Stochastic lookback period", "Indicators")
self._stoch_k_period = self.Param("StochKPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stoch %K Period", "Smoothing period of %K", "Indicators")
self._stoch_d_period = self.Param("StochDPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stoch %D Period", "Period of %D", "Indicators")
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Period for dynamic zones", "Indicators")
self._standard_deviation_factor = self.Param("StandardDeviationFactor", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("StdDev Factor", "Standard deviation factor for dynamic zones", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._k_smoothing = None
self._d_line = None
self._zone_average = None
self._zone_std_dev = None
self._prev_k = None
self._prev_d = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(stochastic_with_dynamic_zones_strategy, self).OnReseted()
self._k_smoothing = None
self._d_line = None
self._zone_average = None
self._zone_std_dev = None
self._prev_k = None
self._prev_d = None
def OnStarted2(self, time):
super(stochastic_with_dynamic_zones_strategy, self).OnStarted2(time)
raw_k = StochasticK()
raw_k.Length = self._stoch_period.Value
self._k_smoothing = SimpleMovingAverage()
self._k_smoothing.Length = self._stoch_k_period.Value
self._d_line = SimpleMovingAverage()
self._d_line.Length = self._stoch_d_period.Value
self._zone_average = SimpleMovingAverage()
self._zone_average.Length = self._lookback_period.Value
self._zone_std_dev = StandardDeviation()
self._zone_std_dev.Length = self._lookback_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(raw_k, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, raw_k_value):
if candle.State != CandleStates.Finished:
return
t = candle.ServerTime
k = float(process_float(self._k_smoothing, raw_k_value, t, True))
if not self._k_smoothing.IsFormed:
return
d = float(process_float(self._d_line, k, t, True))
average = float(process_float(self._zone_average, k, t, True))
std_dev = float(process_float(self._zone_std_dev, k, t, True))
prev_k = self._prev_k
prev_d = self._prev_d
self._prev_k = k
self._prev_d = d
if not self._d_line.IsFormed or not self._zone_average.IsFormed or not self._zone_std_dev.IsFormed:
return
if prev_k is None or prev_d is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
factor = float(self._standard_deviation_factor.Value)
oversold = average - factor * std_dev
overbought = average + factor * std_dev
cross_up = prev_k <= prev_d and k > d
cross_down = prev_k >= prev_d and k < d
if cross_up and k < oversold and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_down and k > overbought and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return stochastic_with_dynamic_zones_strategy()