Открыть на GitHub

Стратегия Ichimoku Stochastic

Стратегия использует облако Ишимоку и осциллятор Стохастик. Длинная позиция открывается, когда цена выше облака, Tenkan больше Kijun, а Стохастик в зоне перепроданности (< 20). Короткая — когда цена ниже облака, Tenkan меньше Kijun и Стохастик в зоне перекупленности (> 80).

Тестирование показывает среднегодичную доходность около 118%. Стратегию лучше запускать на фондовом рынке.

Ишимоку определяет тренд и уровни поддержки, а Стохастик выбирает момент входа на откатах. Сделки открываются, когда осциллятор перезагружается в направлении облака.

Трейдерам, предпочитающим структурированные индикаторы, метод покажется практичным. Стопы по границам облака защищают от резких разворотов.

Подробности

  • Условия входа:
    • Длинная: Price > Cloud && StochK < 20
    • Короткая: Price < Cloud && StochK > 80
  • Long/Short: Оба
  • Условия выхода:
    • выход цены из облака в противоположную сторону
  • Стопы: используются границы облака Ишимоку
  • Параметры по умолчанию:
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouPeriod = 52
    • StochPeriod = 14
    • StochK = 3
    • StochD = 3
    • CandleType = TimeSpan.FromMinutes(30).TimeFrame()
  • Фильтры:
    • Категория: Mean reversion
    • Направление: Оба
    • Индикаторы: Ichimoku Cloud, Stochastic Oscillator
    • Стопы: Да
    • Сложность: Средняя
    • Таймфрейм: Среднесрочный
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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>
/// Strategy based on Ichimoku Cloud and Stochastic Oscillator indicators.
/// Enters long when price is above Kumo (cloud), Tenkan > Kijun, and Stochastic is oversold (< 20)
/// Enters short when price is below Kumo, Tenkan < Kijun, and Stochastic is overbought (> 80)
/// </summary>
public class IchimokuStochasticStrategy : Strategy
{
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouPeriod;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<int> _stochK;
	private readonly StrategyParam<int> _stochD;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;
	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 period
	/// </summary>
	public int SenkouPeriod
	{
		get => _senkouPeriod.Value;
		set => _senkouPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K period
	/// </summary>
	public int StochPeriod
	{
		get => _stochPeriod.Value;
		set => _stochPeriod.Value = value;
	}
	
	/// <summary>
	/// Stochastic %K smoothing 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>
	/// Bars to wait between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Candle type for strategy calculation
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Constructor
	/// </summary>
	public IchimokuStochasticStrategy()
	{
		_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
			.SetGreaterThanZero()
			.SetDisplay("Tenkan-sen Period", "Period for Tenkan-sen line", "Ichimoku")
			
			.SetOptimize(7, 12, 1);

		_kijunPeriod = Param(nameof(KijunPeriod), 26)
			.SetGreaterThanZero()
			.SetDisplay("Kijun-sen Period", "Period for Kijun-sen line", "Ichimoku")
			
			.SetOptimize(20, 30, 2);

		_senkouPeriod = Param(nameof(SenkouPeriod), 52)
			.SetGreaterThanZero()
			.SetDisplay("Senkou Span Period", "Period for Senkou Span B line", "Ichimoku")
			
			.SetOptimize(40, 60, 5);

		_stochPeriod = Param(nameof(StochPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Period", "Period for Stochastic Oscillator", "Stochastic")
			
			.SetOptimize(10, 20, 2);
			
		_stochK = Param(nameof(StochK), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %K", "Smoothing for Stochastic %K line", "Stochastic")
			
			.SetOptimize(1, 5, 1);
			
		_stochD = Param(nameof(StochD), 3)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic %D", "Period for Stochastic %D line", "Stochastic")
			
			.SetOptimize(1, 5, 1);

		_cooldownBars = Param(nameof(CooldownBars), 4)
			.SetRange(1, 20)
			.SetDisplay("Cooldown Bars", "Bars between trades", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for strategy", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
		}

		/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_cooldown = 0;
	}

		/// <inheritdoc />
		protected override void OnStarted2(DateTime time)
		{
		base.OnStarted2(time);

		// Create indicators
		var ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouPeriod }
		};

		var stochastic = new StochasticOscillator
		{
			K = { Length = StochK },
			D = { Length = StochD },
		};

		// Subscribe to candles and bind indicators
		var subscription = SubscribeCandles(CandleType);
		
		subscription
			.BindEx(ichimoku, stochastic, ProcessCandle)
			.Start();

		// Setup chart visualization if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ichimoku);
			
			// Create a separate area for Stochastic
			var stochArea = CreateChartArea();
			if (stochArea != null)
			{
				DrawIndicator(stochArea, stochastic);
			}
			
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue stochValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
			return;
		
		// Check if strategy is ready to trade
		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Get additional values from Ichimoku
		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;

		// Current price (close of the candle)
		var price = candle.ClosePrice;
		
		// Check if price is above/below Kumo cloud
		var isAboveKumo = price > Math.Max(senkouA, senkouB);
		var isBelowKumo = price < Math.Min(senkouA, senkouB);
		
		// Check Tenkan/Kijun cross (trend direction)
		var isBullishCross = tenkan > kijun;
		var isBearishCross = tenkan < kijun;

		var stochTyped = (StochasticOscillatorValue)stochValue;

		// Get Stochastic %K value
		if (stochTyped.K is not decimal stochasticK)
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		// Trading logic
		if (isAboveKumo && isBullishCross && stochasticK < 15 && Position <= 0)
		{
			BuyMarket(Volume + Math.Abs(Position));
			_cooldown = CooldownBars;
		}
		else if (isBelowKumo && isBearishCross && stochasticK > 85 && Position >= 0)
		{
			SellMarket(Volume + Math.Abs(Position));
			_cooldown = CooldownBars;
		}
		else if (isBearishCross && Position > 0)
		{
			SellMarket(Position);
			_cooldown = CooldownBars;
		}
		else if (isBullishCross && Position < 0)
		{
			BuyMarket(Math.Abs(Position));
			_cooldown = CooldownBars;
		}
	}
}