Ichimoku Cloud Buy & Sell
This strategy uses the Ichimoku Cloud with EMA and volume filters. It buys when price is above the cloud with strong volume and sells when price is below the cloud. Positions are closed when price crosses the EMA in the opposite direction.
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>
/// Ichimoku Cloud strategy with EMA and volume filters.
/// Buys above the cloud with strong volume and sells below the cloud.
/// </summary>
public class IchimokuCloudBuySellStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _avgVolumeLength;
private readonly StrategyParam<bool> _useStopLoss;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _requireAboveEma;
private readonly StrategyParam<bool> _requireBelowEma;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevAvgVolume;
private int _cooldown;
/// <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 SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// EMA length for exit filter.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Length for average volume calculation.
/// </summary>
public int AvgVolumeLength
{
get => _avgVolumeLength.Value;
set => _avgVolumeLength.Value = value;
}
/// <summary>
/// Enable stop-loss protection.
/// </summary>
public bool UseStopLoss
{
get => _useStopLoss.Value;
set => _useStopLoss.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Require price above EMA for long entry.
/// </summary>
public bool RequireAboveEma
{
get => _requireAboveEma.Value;
set => _requireAboveEma.Value = value;
}
/// <summary>
/// Require price below EMA for short entry.
/// </summary>
public bool RequireBelowEma
{
get => _requireBelowEma.Value;
set => _requireBelowEma.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public IchimokuCloudBuySellStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Tenkan-sen periods", "Ichimoku Settings")
.SetOptimize(7, 11, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Kijun-sen periods", "Ichimoku Settings")
.SetOptimize(20, 30, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Senkou Span B periods", "Ichimoku Settings")
.SetOptimize(40, 60, 4);
_emaPeriod = Param(nameof(EmaPeriod), 44)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "EMA length for exit", "Filters")
.SetOptimize(20, 80, 5);
_avgVolumeLength = Param(nameof(AvgVolumeLength), 10)
.SetGreaterThanZero()
.SetDisplay("Average Volume Length", "Length for average volume filter", "Filters")
.SetOptimize(5, 20, 5);
_useStopLoss = Param(nameof(UseStopLoss), true)
.SetDisplay("Use Stop Loss", "Enable stop-loss exits", "Risk Management");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop-loss percentage", "Risk Management")
.SetOptimize(1m, 5m, 0.5m);
_requireAboveEma = Param(nameof(RequireAboveEma), true)
.SetDisplay("Only Buy Above EMA", "Require price above EMA for long entries", "Filters");
_requireBelowEma = Param(nameof(RequireBelowEma), true)
.SetDisplay("Only Sell Below EMA", "Require price below EMA for short entries", "Filters");
_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();
_prevAvgVolume = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var volumeMa = new SimpleMovingAverage { Length = AvgVolumeLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ichimoku, ema, volumeMa, ProcessCandle)
.Start();
if (UseStopLoss)
StartProtection(takeProfit: default, stopLoss: new Unit(StopLossPercent, UnitTypes.Percent));
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue emaValue, IIndicatorValue volumeValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
if (emaValue.IsEmpty || volumeValue.IsEmpty || ichimokuValue.IsEmpty)
return;
var ema = emaValue.ToDecimal();
var avgVol = volumeValue.ToDecimal();
_prevAvgVolume = avgVol;
if (ichimokuValue is not IchimokuValue ichi)
return;
if (ichi.SenkouA is not decimal senkouA ||
ichi.SenkouB is not decimal senkouB)
return;
var upperKumo = Math.Max(senkouA, senkouB);
var lowerKumo = Math.Min(senkouA, senkouB);
var buyCondition = candle.ClosePrice > upperKumo && (!RequireAboveEma || candle.ClosePrice > ema);
var sellCondition = candle.ClosePrice < lowerKumo && (!RequireBelowEma || candle.ClosePrice < ema);
if (buyCondition && Position <= 0)
{
BuyMarket();
_cooldown = 5;
}
else if (sellCondition && Position >= 0)
{
SellMarket();
_cooldown = 5;
}
else if (Position > 0 && candle.ClosePrice < ema)
{
SellMarket();
_cooldown = 5;
}
else if (Position < 0 && candle.ClosePrice > ema)
{
BuyMarket();
_cooldown = 5;
}
}
}
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 Ichimoku, ExponentialMovingAverage, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ichimoku_cloud_buy_sell_strategy(Strategy):
def __init__(self):
super(ichimoku_cloud_buy_sell_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan Period", "Tenkan-sen periods", "Ichimoku Settings")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun Period", "Kijun-sen periods", "Ichimoku Settings")
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Senkou Span B periods", "Ichimoku Settings")
self._ema_period = self.Param("EmaPeriod", 44) \
.SetGreaterThanZero() \
.SetDisplay("EMA Period", "EMA length for exit", "Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._cooldown = 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(ichimoku_cloud_buy_sell_strategy, self).OnReseted()
self._cooldown = 0
def OnStarted2(self, time):
super(ichimoku_cloud_buy_sell_strategy, self).OnStarted2(time)
self._ichimoku = Ichimoku()
self._ichimoku.Tenkan.Length = self._tenkan_period.Value
self._ichimoku.Kijun.Length = self._kijun_period.Value
self._ichimoku.SenkouB.Length = self._senkou_span_b_period.Value
self._ema = ExponentialMovingAverage()
self._ema.Length = self._ema_period.Value
volume_ma = SimpleMovingAverage()
volume_ma.Length = 10
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._ichimoku, self._ema, volume_ma, self.OnProcess).Start()
self.StartProtection(None, Unit(2, UnitTypes.Percent))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ichimoku_val, ema_val, volume_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown > 0:
self._cooldown -= 1
return
if ema_val.IsEmpty or ichimoku_val.IsEmpty:
return
ema_v = float(ema_val)
senkou_a = ichimoku_val.SenkouA
senkou_b = ichimoku_val.SenkouB
if senkou_a is None or senkou_b is None:
return
senkou_a_v = float(senkou_a)
senkou_b_v = float(senkou_b)
upper_kumo = max(senkou_a_v, senkou_b_v)
lower_kumo = min(senkou_a_v, senkou_b_v)
close = float(candle.ClosePrice)
buy_cond = close > upper_kumo and close > ema_v
sell_cond = close < lower_kumo and close < ema_v
if buy_cond and self.Position <= 0:
self.BuyMarket()
self._cooldown = 5
elif sell_cond and self.Position >= 0:
self.SellMarket()
self._cooldown = 5
elif self.Position > 0 and close < ema_v:
self.SellMarket()
self._cooldown = 5
elif self.Position < 0 and close > ema_v:
self.BuyMarket()
self._cooldown = 5
def CreateClone(self):
return ichimoku_cloud_buy_sell_strategy()