Стратегия Ichimoku Chinkou Cross
Стратегия торгует по пересечению запаздывающей линии Ichimoku (Chinkou Span) с ценой.
Логика стратегии
- Покупка: Chinkou пересекает цену снизу вверх, текущая цена и Chinkou находятся выше облака Kumo, а RSI выше
RsiBuyLevel. - Продажа: Chinkou пересекает цену сверху вниз, текущая цена и Chinkou ниже облака Kumo, а RSI ниже
RsiSellLevel.
Стратегия использует защитный стоп через StartProtection и параметры Tenkan, Kijun, Senkou Span B и RSI.
Параметры
| Имя | Описание | Значение |
|---|---|---|
TenkanPeriod |
Период Tenkan-sen | 9 |
KijunPeriod |
Период Kijun-sen | 26 |
SenkouSpanPeriod |
Период Senkou Span B | 52 |
RsiPeriod |
Период расчёта RSI | 14 |
RsiBuyLevel |
Минимальный RSI для покупок | 70 |
RsiSellLevel |
Максимальный RSI для продаж | 30 |
StopLoss |
Процент или величина стоп-лосса | 2% |
CandleType |
Тип свечей | 5-минутные свечи |
Индикаторы
- Ichimoku
- RSI
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 Chinkou Span crossover strategy with RSI filter.
/// Buys when Chinkou crosses price from below above the Kumo and RSI is high.
/// Sells when Chinkou crosses price from above below the Kumo and RSI is low.
/// </summary>
public class IchimokuChinkouCrossStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiBuyLevel;
private readonly StrategyParam<decimal> _rsiSellLevel;
private readonly StrategyParam<DataType> _candleType;
private RelativeStrengthIndex _rsi;
private readonly List<decimal> _closes = new();
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
/// <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 SenkouSpanPeriod { get => _senkouSpanPeriod.Value; set => _senkouSpanPeriod.Value = value; }
/// <summary>
/// RSI calculation period.
/// </summary>
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
/// <summary>
/// RSI threshold for long signals.
/// </summary>
public decimal RsiBuyLevel { get => _rsiBuyLevel.Value; set => _rsiBuyLevel.Value = value; }
/// <summary>
/// RSI threshold for short signals.
/// </summary>
public decimal RsiSellLevel { get => _rsiSellLevel.Value; set => _rsiSellLevel.Value = value; }
/// <summary>
/// Candle type for processing.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Initialize <see cref="IchimokuChinkouCrossStrategy"/>.
/// </summary>
public IchimokuChinkouCrossStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Tenkan-sen period", "Ichimoku");
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Kijun-sen period", "Ichimoku");
_senkouSpanPeriod = Param(nameof(SenkouSpanPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span Period", "Senkou Span B period", "Ichimoku");
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI calculation period", "RSI");
_rsiBuyLevel = Param(nameof(RsiBuyLevel), 55m)
.SetDisplay("RSI Buy Level", "Minimum RSI for long", "RSI");
_rsiSellLevel = Param(nameof(RsiSellLevel), 45m)
.SetDisplay("RSI Sell Level", "Maximum RSI for short", "RSI");
_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();
_closes.Clear();
_highs.Clear();
_lows.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, ProcessCandle)
.Start();
StartProtection(
new Unit(2000m, UnitTypes.Absolute),
new Unit(1000m, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
var maxCount = Math.Max(SenkouSpanPeriod, KijunPeriod) + KijunPeriod + 2;
if (_closes.Count > maxCount)
{
_closes.RemoveAt(0);
_highs.RemoveAt(0);
_lows.RemoveAt(0);
}
if (_closes.Count <= KijunPeriod || _closes.Count < Math.Max(TenkanPeriod, KijunPeriod))
return;
var tenkan = GetMidpoint(TenkanPeriod);
var kijun = GetMidpoint(KijunPeriod);
var lastIndex = _closes.Count - 1;
var prevIndex = lastIndex - 1;
var lagIndex = lastIndex - KijunPeriod;
var prevLagIndex = prevIndex - KijunPeriod;
if (prevLagIndex < 0)
return;
var close = _closes[lastIndex];
var prevClose = _closes[prevIndex];
var lagClose = _closes[lagIndex];
var prevLagClose = _closes[prevLagIndex];
var chinkouCrossUp = close > lagClose && prevClose <= prevLagClose;
var chinkouCrossDown = close < lagClose && prevClose >= prevLagClose;
if (chinkouCrossUp && tenkan > kijun && rsiValue >= RsiBuyLevel && Position <= 0)
BuyMarket();
else if (chinkouCrossDown && tenkan < kijun && rsiValue <= RsiSellLevel && Position >= 0)
SellMarket();
}
private decimal GetMidpoint(int period)
{
var start = _highs.Count - period;
var highest = _highs[start];
var lowest = _lows[start];
for (var i = start + 1; i < _highs.Count; i++)
{
if (_highs[i] > highest)
highest = _highs[i];
if (_lows[i] < lowest)
lowest = _lows[i];
}
return (highest + lowest) / 2m;
}
}
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 RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
class ichimoku_chinkou_cross_strategy(Strategy):
def __init__(self):
super(ichimoku_chinkou_cross_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9)
self._kijun_period = self.Param("KijunPeriod", 26)
self._senkou_span_period = self.Param("SenkouSpanPeriod", 52)
self._rsi_period = self.Param("RsiPeriod", 14)
self._rsi_buy_level = self.Param("RsiBuyLevel", 55.0)
self._rsi_sell_level = self.Param("RsiSellLevel", 45.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._closes = []
self._highs = []
self._lows = []
@property
def TenkanPeriod(self):
return self._tenkan_period.Value
@TenkanPeriod.setter
def TenkanPeriod(self, value):
self._tenkan_period.Value = value
@property
def KijunPeriod(self):
return self._kijun_period.Value
@KijunPeriod.setter
def KijunPeriod(self, value):
self._kijun_period.Value = value
@property
def SenkouSpanPeriod(self):
return self._senkou_span_period.Value
@SenkouSpanPeriod.setter
def SenkouSpanPeriod(self, value):
self._senkou_span_period.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiBuyLevel(self):
return self._rsi_buy_level.Value
@RsiBuyLevel.setter
def RsiBuyLevel(self, value):
self._rsi_buy_level.Value = value
@property
def RsiSellLevel(self):
return self._rsi_sell_level.Value
@RsiSellLevel.setter
def RsiSellLevel(self, value):
self._rsi_sell_level.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _get_midpoint(self, period):
start = len(self._highs) - period
highest = self._highs[start]
lowest = self._lows[start]
for i in range(start + 1, len(self._highs)):
if self._highs[i] > highest:
highest = self._highs[i]
if self._lows[i] < lowest:
lowest = self._lows[i]
return (highest + lowest) / 2.0
def OnStarted2(self, time):
super(ichimoku_chinkou_cross_strategy, self).OnStarted2(time)
self._closes = []
self._highs = []
self._lows = []
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle, rsi_value):
if candle.State != CandleStates.Finished:
return
self._closes.append(float(candle.ClosePrice))
self._highs.append(float(candle.HighPrice))
self._lows.append(float(candle.LowPrice))
kijun_p = int(self.KijunPeriod)
senkou_p = int(self.SenkouSpanPeriod)
tenkan_p = int(self.TenkanPeriod)
max_count = max(senkou_p, kijun_p) + kijun_p + 2
if len(self._closes) > max_count:
self._closes.pop(0)
self._highs.pop(0)
self._lows.pop(0)
if len(self._closes) <= kijun_p or len(self._closes) < max(tenkan_p, kijun_p):
return
tenkan = self._get_midpoint(tenkan_p)
kijun = self._get_midpoint(kijun_p)
last_idx = len(self._closes) - 1
prev_idx = last_idx - 1
lag_idx = last_idx - kijun_p
prev_lag_idx = prev_idx - kijun_p
if prev_lag_idx < 0:
return
close = self._closes[last_idx]
prev_close = self._closes[prev_idx]
lag_close = self._closes[lag_idx]
prev_lag_close = self._closes[prev_lag_idx]
chinkou_cross_up = close > lag_close and prev_close <= prev_lag_close
chinkou_cross_down = close < lag_close and prev_close >= prev_lag_close
rsi_val = float(rsi_value)
if chinkou_cross_up and tenkan > kijun and rsi_val >= float(self.RsiBuyLevel) and self.Position <= 0:
self.BuyMarket()
elif chinkou_cross_down and tenkan < kijun and rsi_val <= float(self.RsiSellLevel) and self.Position >= 0:
self.SellMarket()
def OnReseted(self):
super(ichimoku_chinkou_cross_strategy, self).OnReseted()
self._closes = []
self._highs = []
self._lows = []
def CreateClone(self):
return ichimoku_chinkou_cross_strategy()