KumoTrade Ichimoku Strategy
Strategy based on Ichimoku Cloud and Stochastic Oscillator. It enters long when price pulls back above Kijun with oversold Stochastic and no cloud ahead. It enters short when price drops below the cloud with overbought Stochastic and bearish Kumo.
Details
- Entry Criteria:
- Long:
Low > Kijun && Kijun > Tenkan && Close < SenkouA && StochD < 29 - Short:
Close < min(SenkouA, SenkouB) && High > Kijun && prevStochD > StochD >= 90
- Long:
- Long/Short: Both
- Exit Criteria:
- ATR based trailing stop
- Stops: Trailing stop using ATR * 3
- Default Values:
TenkanPeriod= 9KijunPeriod= 26SenkouPeriod= 52StochK= 70StochD= 15AtrPeriod= 5CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Ichimoku Cloud, Stochastic, ATR
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Short-term
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Ichimoku strategy with Stochastic oscillator and ATR trailing stop.
/// </summary>
public class KumoTradeIchimokuStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouPeriod;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<int> _stochD;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _maxEntries;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DateTimeOffset> _startTime;
private readonly StrategyParam<DateTimeOffset> _endTime;
private decimal _prevStochD;
private decimal _prevHigh;
private decimal _prevKijun;
private decimal? _trailStopLong;
private decimal? _trailStopShort;
private decimal _highestClose;
private decimal _lowestLow;
private int _entriesExecuted;
private int _barsSinceSignal;
/// <summary>
/// Tenkan-sen period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B period.
/// </summary>
public int SenkouPeriod
{
get => _senkouPeriod.Value;
set => _senkouPeriod.Value = value;
}
/// <summary>
/// Stochastic %K period.
/// </summary>
public int StochK
{
get => _stochK.Value;
set => _stochK.Value = value;
}
/// <summary>
/// Stochastic %D period.
/// </summary>
public int StochD
{
get => _stochD.Value;
set => _stochD.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Trading start time.
/// </summary>
public DateTimeOffset StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>
/// Trading end time.
/// </summary>
public DateTimeOffset EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>
/// Maximum entries per run.
/// </summary>
public int MaxEntries
{
get => _maxEntries.Value;
set => _maxEntries.Value = value;
}
/// <summary>
/// Minimum bars between entries.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="KumoTradeIchimokuStrategy"/>.
/// </summary>
public KumoTradeIchimokuStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetDisplay("Tenkan-sen Period", "Period for Tenkan line", "Ichimoku")
.SetOptimize(7, 13, 2);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetDisplay("Kijun-sen Period", "Period for Kijun line", "Ichimoku")
.SetOptimize(20, 30, 2);
_senkouPeriod = Param(nameof(SenkouPeriod), 52)
.SetDisplay("Senkou Span B Period", "Period for Senkou B", "Ichimoku")
.SetOptimize(40, 60, 4);
_stochK = Param(nameof(StochK), 70)
.SetDisplay("Stochastic %K", "Period for %K line", "Stochastic")
.SetOptimize(50, 90, 5);
_stochD = Param(nameof(StochD), 15)
.SetDisplay("Stochastic %D", "Smoothing for %D line", "Stochastic")
.SetOptimize(10, 25, 5);
_atrPeriod = Param(nameof(AtrPeriod), 5)
.SetDisplay("ATR Period", "Period for ATR stop", "Risk")
.SetOptimize(3, 10, 1);
_maxEntries = Param(nameof(MaxEntries), 45)
.SetGreaterThanZero()
.SetDisplay("Max Entries", "Maximum entries per run", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 12000)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Minimum bars between entries", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_startTime = Param(nameof(StartTime), DateTimeOffset.MinValue)
.SetDisplay("Start Time", "Trading window start", "Time");
_endTime = Param(nameof(EndTime), DateTimeOffset.MaxValue)
.SetDisplay("End Time", "Trading window end", "Time");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevStochD = 0m;
_prevHigh = 0m;
_prevKijun = 0m;
_trailStopLong = null;
_trailStopShort = null;
_highestClose = 0m;
_lowestLow = 0m;
_entriesExecuted = 0;
_barsSinceSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entriesExecuted = 0;
_barsSinceSignal = CooldownBars;
var ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouPeriod }
};
var stochastic = new StochasticOscillator
{
K = { Length = StochK },
D = { Length = StochD },
};
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ichimoku, stochastic, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ichimoku);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue stochValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceSignal++;
if (candle.OpenTime < StartTime || candle.OpenTime > EndTime)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ichimokuTyped = (IchimokuValue)ichimokuValue;
if (ichimokuTyped.Tenkan is not decimal tenkan)
return;
if (ichimokuTyped.Kijun is not decimal kijun)
return;
if (ichimokuTyped.SenkouA is not decimal senkouA)
return;
if (ichimokuTyped.SenkouB is not decimal senkouB)
return;
var stochTyped = (StochasticOscillatorValue)stochValue;
if (stochTyped.D is not decimal stochD)
return;
var atr = atrValue.ToDecimal();
var upperCloud = Math.Max(senkouA, senkouB);
var lowerCloud = Math.Min(senkouA, senkouB);
var noKumo = candle.HighPrice < (lowerCloud - atr / 2m) || candle.LowPrice > (upperCloud + atr);
var kumoRed = senkouB > senkouA;
var longCond = Position <= 0 &&
candle.LowPrice > kijun &&
kijun > tenkan &&
candle.ClosePrice < senkouA &&
candle.ClosePrice > candle.OpenPrice &&
noKumo &&
stochD < 29m;
var crossedAboveKijun = candle.HighPrice > kijun && _prevHigh <= _prevKijun;
var shortCond = Position >= 0 &&
candle.ClosePrice < lowerCloud &&
crossedAboveKijun &&
stochD >= 90m &&
_prevStochD > stochD &&
kumoRed;
if (_barsSinceSignal < CooldownBars)
{
_prevStochD = stochD;
_prevHigh = candle.HighPrice;
_prevKijun = kijun;
return;
}
if (_entriesExecuted < MaxEntries && _barsSinceSignal >= CooldownBars && longCond)
{
BuyMarket(Volume + Math.Abs(Position));
_trailStopLong = null;
_highestClose = candle.ClosePrice;
_entriesExecuted++;
_barsSinceSignal = 0;
}
else if (_entriesExecuted < MaxEntries && _barsSinceSignal >= CooldownBars && shortCond)
{
SellMarket(Volume + Math.Abs(Position));
_trailStopShort = null;
_lowestLow = candle.LowPrice;
_entriesExecuted++;
_barsSinceSignal = 0;
}
if (Position > 0)
{
_highestClose = Math.Max(_highestClose, candle.ClosePrice);
var temp = _highestClose - atr * 3m;
if (_trailStopLong == null || temp > _trailStopLong)
_trailStopLong = temp;
if (_trailStopLong != null && candle.ClosePrice < _trailStopLong)
{
SellMarket(Math.Abs(Position));
_trailStopLong = null;
_highestClose = 0m;
_barsSinceSignal = 0;
}
}
else if (Position < 0)
{
_lowestLow = Math.Min(_lowestLow, candle.LowPrice);
var temp = _lowestLow + atr * 3m;
if (_trailStopShort == null || temp < _trailStopShort)
_trailStopShort = temp;
if (_trailStopShort != null && candle.ClosePrice > _trailStopShort)
{
BuyMarket(Math.Abs(Position));
_trailStopShort = null;
_lowestLow = 0m;
_barsSinceSignal = 0;
}
}
_prevStochD = stochD;
_prevHigh = candle.HighPrice;
_prevKijun = kijun;
}
}
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 Ichimoku, StochasticOscillator, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class kumo_trade_ichimoku_strategy(Strategy):
def __init__(self):
super(kumo_trade_ichimoku_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetDisplay("Tenkan-sen Period", "Period for Tenkan line", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetDisplay("Kijun-sen Period", "Period for Kijun line", "Ichimoku")
self._senkou_period = self.Param("SenkouPeriod", 52) \
.SetDisplay("Senkou Span B Period", "Period for Senkou B", "Ichimoku")
self._stoch_k = self.Param("StochK", 70) \
.SetDisplay("Stochastic K", "Period for K line", "Stochastic")
self._stoch_d = self.Param("StochD", 15) \
.SetDisplay("Stochastic D", "Smoothing for D line", "Stochastic")
self._atr_period = self.Param("AtrPeriod", 5) \
.SetDisplay("ATR Period", "Period for ATR stop", "Risk")
self._max_entries = self.Param("MaxEntries", 45) \
.SetGreaterThanZero() \
.SetDisplay("Max Entries", "Maximum entries per run", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 12000) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Minimum bars between entries", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_stoch_d = 0.0
self._prev_high = 0.0
self._prev_kijun = 0.0
self._trail_stop_long = None
self._trail_stop_short = None
self._highest_close = 0.0
self._lowest_low = 0.0
self._entries_executed = 0
self._bars_since_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(kumo_trade_ichimoku_strategy, self).OnReseted()
self._prev_stoch_d = 0.0
self._prev_high = 0.0
self._prev_kijun = 0.0
self._trail_stop_long = None
self._trail_stop_short = None
self._highest_close = 0.0
self._lowest_low = 0.0
self._entries_executed = 0
self._bars_since_signal = 0
def OnStarted2(self, time):
super(kumo_trade_ichimoku_strategy, self).OnStarted2(time)
self._entries_executed = 0
self._bars_since_signal = self._cooldown_bars.Value
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self._tenkan_period.Value
ichimoku.Kijun.Length = self._kijun_period.Value
ichimoku.SenkouB.Length = self._senkou_period.Value
stoch = StochasticOscillator()
stoch.K.Length = self._stoch_k.Value
stoch.D.Length = self._stoch_d.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ichimoku, stoch, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ichimoku_val, stoch_val, atr_val):
if candle.State != CandleStates.Finished:
return
self._bars_since_signal += 1
tenkan = ichimoku_val.Tenkan
kijun = ichimoku_val.Kijun
senkou_a = ichimoku_val.SenkouA
senkou_b = ichimoku_val.SenkouB
if tenkan is None or kijun is None or senkou_a is None or senkou_b is None:
return
tenkan = float(tenkan)
kijun = float(kijun)
senkou_a = float(senkou_a)
senkou_b = float(senkou_b)
stoch_d_val = stoch_val.D
if stoch_d_val is None:
return
stoch_d = float(stoch_d_val)
atr_v = float(atr_val)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
open_p = float(candle.OpenPrice)
upper_cloud = max(senkou_a, senkou_b)
lower_cloud = min(senkou_a, senkou_b)
no_kumo = high < (lower_cloud - atr_v / 2.0) or low > (upper_cloud + atr_v)
kumo_red = senkou_b > senkou_a
long_cond = (self.Position <= 0 and low > kijun and kijun > tenkan
and close < senkou_a and close > open_p and no_kumo and stoch_d < 29.0)
crossed_above_kijun = high > kijun and self._prev_high <= self._prev_kijun
short_cond = (self.Position >= 0 and close < lower_cloud and crossed_above_kijun
and stoch_d >= 90.0 and self._prev_stoch_d > stoch_d and kumo_red)
if self._bars_since_signal < self._cooldown_bars.Value:
self._prev_stoch_d = stoch_d
self._prev_high = high
self._prev_kijun = kijun
return
if self._entries_executed < self._max_entries.Value and long_cond:
self.BuyMarket()
self._trail_stop_long = None
self._highest_close = close
self._entries_executed += 1
self._bars_since_signal = 0
elif self._entries_executed < self._max_entries.Value and short_cond:
self.SellMarket()
self._trail_stop_short = None
self._lowest_low = low
self._entries_executed += 1
self._bars_since_signal = 0
if self.Position > 0:
if close > self._highest_close:
self._highest_close = close
temp = self._highest_close - atr_v * 3.0
if self._trail_stop_long is None or temp > self._trail_stop_long:
self._trail_stop_long = temp
if self._trail_stop_long is not None and close < self._trail_stop_long:
self.SellMarket()
self._trail_stop_long = None
self._highest_close = 0.0
self._bars_since_signal = 0
elif self.Position < 0:
if low < self._lowest_low or self._lowest_low == 0.0:
self._lowest_low = low
temp = self._lowest_low + atr_v * 3.0
if self._trail_stop_short is None or temp < self._trail_stop_short:
self._trail_stop_short = temp
if self._trail_stop_short is not None and close > self._trail_stop_short:
self.BuyMarket()
self._trail_stop_short = None
self._lowest_low = 0.0
self._bars_since_signal = 0
self._prev_stoch_d = stoch_d
self._prev_high = high
self._prev_kijun = kijun
def CreateClone(self):
return kumo_trade_ichimoku_strategy()