Stufic Stochastic Strategy
This strategy combines trend detection using two moving averages with momentum signals from the Stochastic oscillator. It buys when the fast moving average is above the slow moving average and the Stochastic %K line crosses above the %D line below an oversold threshold. It sells when the fast moving average is below the slow moving average and %K crosses below %D above an overbought threshold.
Logic
- Detects market trend by comparing a fast and a slow simple moving average.
- Uses Stochastic oscillator to find momentum reversals at extreme levels.
- Opens a long position when the trend is up and the oscillator exits the oversold zone with a bullish crossover.
- Opens a short position when the trend is down and the oscillator exits the overbought zone with a bearish crossover.
- Positions are closed or reversed on opposite signals. A stop-loss percentage is applied using built-in protection.
Parameters
- FastMaPeriod – period of the fast moving average.
- SlowMaPeriod – period of the slow moving average.
- StochKPeriod – period for the %K line of the Stochastic.
- StochDPeriod – smoothing period for the %D line.
- OverboughtLevel – upper threshold for the Stochastic oscillator.
- OversoldLevel – lower threshold for the Stochastic oscillator.
- StopLossPercent – stop-loss distance expressed as percentage of entry price.
- CandleType – candle series used for calculations.
Indicators
- Simple Moving Average (fast and slow).
- Stochastic Oscillator.
Usage
Attach the strategy to a security. Configure the parameters to match the desired timeframe and risk level. Start the strategy to begin trading. The algorithm automatically manages positions based on the described conditions.
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>
/// Strategy based on fast/slow moving averages and Stochastic oscillator.
/// Buys when %K crosses above %D below oversold level while trend is up.
/// Sells when %K crosses below %D above overbought level while trend is down.
/// </summary>
public class StuficStochStrategy : Strategy
{
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<int> _stochKPeriod;
private readonly StrategyParam<int> _stochDPeriod;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _fastMa;
private SimpleMovingAverage _slowMa;
private StochasticOscillator _stochastic;
private decimal _prevK;
private decimal _prevD;
private bool _isFirst = true;
public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
public int StochKPeriod { get => _stochKPeriod.Value; set => _stochKPeriod.Value = value; }
public int StochDPeriod { get => _stochDPeriod.Value; set => _stochDPeriod.Value = value; }
public decimal OverboughtLevel { get => _overboughtLevel.Value; set => _overboughtLevel.Value = value; }
public decimal OversoldLevel { get => _oversoldLevel.Value; set => _oversoldLevel.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public StuficStochStrategy()
{
_fastMaPeriod = Param(nameof(FastMaPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Fast moving average period", "Indicators");
_slowMaPeriod = Param(nameof(SlowMaPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Slow moving average period", "Indicators");
_stochKPeriod = Param(nameof(StochKPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stoch %K", "%K period for Stochastic", "Indicators");
_stochDPeriod = Param(nameof(StochDPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Stoch %D", "%D period for Stochastic", "Indicators");
_overboughtLevel = Param(nameof(OverboughtLevel), 80m)
.SetDisplay("Overbought", "Overbought level", "Trading");
_oversoldLevel = Param(nameof(OversoldLevel), 20m)
.SetDisplay("Oversold", "Oversold level", "Trading");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).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();
_fastMa = default;
_slowMa = default;
_stochastic = default;
_prevK = 0;
_prevD = 0;
_isFirst = true;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = new SimpleMovingAverage { Length = FastMaPeriod };
_slowMa = new SimpleMovingAverage { Length = SlowMaPeriod };
_stochastic = new StochasticOscillator
{
K = { Length = StochKPeriod },
D = { Length = StochDPeriod },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_stochastic, ProcessCandle)
.Start();
StartProtection(
stopLoss: new Unit(StopLossPercent, UnitTypes.Percent),
takeProfit: null
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
// process MAs manually
var fastResult = _fastMa.Process(candle.ClosePrice, candle.OpenTime, true);
var slowResult = _slowMa.Process(candle.ClosePrice, candle.OpenTime, true);
if (!fastResult.IsFormed || !slowResult.IsFormed)
return;
var fast = fastResult.ToDecimal();
var slow = slowResult.ToDecimal();
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal k || stoch.D is not decimal d)
return;
if (_isFirst)
{
_prevK = k;
_prevD = d;
_isFirst = false;
return;
}
// Bullish: %K crosses above %D in oversold zone, trend up
if (_prevK <= _prevD && k > d && k < OversoldLevel && fast > slow && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Bearish: %K crosses below %D in overbought zone, trend down
else if (_prevK >= _prevD && k < d && k > OverboughtLevel && fast < slow && Position >= 0)
{
if (Position > 0) SellMarket();
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class stufic_stoch_strategy(Strategy):
def __init__(self):
super(stufic_stoch_strategy, self).__init__()
self._fast_ma_period = self.Param("FastMaPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Fast MA", "Fast moving average period", "Indicators")
self._slow_ma_period = self.Param("SlowMaPeriod", 30) \
.SetGreaterThanZero() \
.SetDisplay("Slow MA", "Slow moving average period", "Indicators")
self._stoch_k_period = self.Param("StochKPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Stoch %K", "%K period for Stochastic", "Indicators")
self._stoch_d_period = self.Param("StochDPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Stoch %D", "%D period for Stochastic", "Indicators")
self._overbought_level = self.Param("OverboughtLevel", 80.0) \
.SetDisplay("Overbought", "Overbought level", "Trading")
self._oversold_level = self.Param("OversoldLevel", 20.0) \
.SetDisplay("Oversold", "Oversold level", "Trading")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._fast_ma = None
self._slow_ma = None
self._prev_k = 0.0
self._prev_d = 0.0
self._is_first = True
@property
def fast_ma_period(self):
return self._fast_ma_period.Value
@property
def slow_ma_period(self):
return self._slow_ma_period.Value
@property
def stoch_k_period(self):
return self._stoch_k_period.Value
@property
def stoch_d_period(self):
return self._stoch_d_period.Value
@property
def overbought_level(self):
return self._overbought_level.Value
@property
def oversold_level(self):
return self._oversold_level.Value
@property
def stop_loss_percent(self):
return self._stop_loss_percent.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(stufic_stoch_strategy, self).OnReseted()
self._prev_k = 0.0
self._prev_d = 0.0
self._is_first = True
def OnStarted2(self, time):
super(stufic_stoch_strategy, self).OnStarted2(time)
self._fast_ma = SimpleMovingAverage()
self._fast_ma.Length = self.fast_ma_period
self._slow_ma = SimpleMovingAverage()
self._slow_ma.Length = self.slow_ma_period
stochastic = StochasticOscillator()
stochastic.K.Length = self.stoch_k_period
stochastic.D.Length = self.stoch_d_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(stochastic, self.process_candle).Start()
self.StartProtection(
None,
Unit(float(self.stop_loss_percent), UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._fast_ma)
self.DrawIndicator(area, self._slow_ma)
self.DrawIndicator(area, stochastic)
self.DrawOwnTrades(area)
def process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
fast_result = process_float(self._fast_ma, candle.ClosePrice, candle.OpenTime, True)
slow_result = process_float(self._slow_ma, candle.ClosePrice, candle.OpenTime, True)
if not fast_result.IsFormed or not slow_result.IsFormed:
return
fast = float(fast_result)
slow = float(slow_result)
k_val = stoch_value.K
d_val = stoch_value.D
if k_val is None or d_val is None:
return
k = float(k_val)
d = float(d_val)
if self._is_first:
self._prev_k = k
self._prev_d = d
self._is_first = False
return
ob = float(self.overbought_level)
os_lvl = float(self.oversold_level)
# Bullish: %K crosses above %D in oversold zone, trend up
if self._prev_k <= self._prev_d and k > d and k < os_lvl and fast > slow and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Bearish: %K crosses below %D in overbought zone, trend down
elif self._prev_k >= self._prev_d and k < d and k > ob and fast < slow and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_k = k
self._prev_d = d
def CreateClone(self):
return stufic_stoch_strategy()