Открыть на GitHub

Стратегия Ichimoku Clouds Long and Short

Эта стратегия использует пересечение Tenkan-sen и Kijun-sen индикатора Ichimoku. Пересечения классифицируются как сильные, нейтральные или слабые в зависимости от положения Tenkan относительно облака. В выбранном торговом режиме открываются длинные или короткие позиции, когда появляется сигнал нужной силы. Дополнительно можно задать проценты тейк-профита и стоп-лосса для закрытия позиции или использовать противоположный сигнал.

Подробности

  • Условия входа:
    • Tenkan-sen пересекает Kijun-sen вверх и сила сигнала соответствует выбранным опциям для лонга.
    • Tenkan-sen пересекает Kijun-sen вниз и сила сигнала соответствует выбранным опциям для шорта.
  • Направление: Настраивается, по умолчанию лонг.
  • Условия выхода:
    • Противоположные сигналы согласно заданным настройкам.
    • Процентный тейк-профит или стоп-лосс.
  • Стопы: Процентные тейк-профит и стоп-лосс.
  • Параметры по умолчанию:
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouSpanPeriod = 52
    • TakeProfitPct = 0
    • StopLossPct = 0
  • Фильтры:
    • Категория: Тренд
    • Направление: Лонг и шорт
    • Индикаторы: Ichimoku
    • Стопы: Опционально
    • Сложность: Средняя
    • Таймфрейм: Любой
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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 Clouds strategy allowing long or short modes with customizable signal strength filters and optional take-profit / stop-loss levels.
/// Entry is triggered by Tenkan-sen crossing Kijun-sen and classified as strong, neutral or weak depending on the cloud position.
/// </summary>
public class IchimokuCloudsLongAndShortStrategy : Strategy
{
	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanPeriod;
	private readonly StrategyParam<decimal> _takeProfitPct;
	private readonly StrategyParam<decimal> _stopLossPct;
	private readonly StrategyParam<string> _tradeDirection;
	private readonly StrategyParam<string> _entrySignalOptionsLong;
	private readonly StrategyParam<string> _exitSignalOptionsLong;
	private readonly StrategyParam<string> _entrySignalOptionsShort;
	private readonly StrategyParam<string> _exitSignalOptionsShort;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevTenkan;
	private decimal _entryPrice;
	private decimal _prevKijun;

	/// <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>
	/// Take profit percentage.
	/// </summary>
	public decimal TakeProfitPct
	{
		get => _takeProfitPct.Value;
		set => _takeProfitPct.Value = value;
	}

	/// <summary>
	/// Stop loss percentage.
	/// </summary>
	public decimal StopLossPct
	{
		get => _stopLossPct.Value;
		set => _stopLossPct.Value = value;
	}

	/// <summary>
	/// Trading mode (Long or Short).
	/// </summary>
	public string TradeDirection
	{
		get => _tradeDirection.Value;
		set => _tradeDirection.Value = value;
	}

	/// <summary>
	/// Entry signal options when trading long.
	/// </summary>
	public string EntrySignalOptionsLong
	{
		get => _entrySignalOptionsLong.Value;
		set => _entrySignalOptionsLong.Value = value;
	}

	/// <summary>
	/// Exit signal options when trading long.
	/// </summary>
	public string ExitSignalOptionsLong
	{
		get => _exitSignalOptionsLong.Value;
		set => _exitSignalOptionsLong.Value = value;
	}

	/// <summary>
	/// Entry signal options when trading short.
	/// </summary>
	public string EntrySignalOptionsShort
	{
		get => _entrySignalOptionsShort.Value;
		set => _entrySignalOptionsShort.Value = value;
	}

	/// <summary>
	/// Exit signal options when trading short.
	/// </summary>
	public string ExitSignalOptionsShort
	{
		get => _exitSignalOptionsShort.Value;
		set => _exitSignalOptionsShort.Value = value;
	}

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

	/// <summary>
	/// Initialize Ichimoku Clouds Long/Short strategy.
	/// </summary>
	public IchimokuCloudsLongAndShortStrategy()
	{
		_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
		.SetGreaterThanZero()
		.SetDisplay("Tenkan Period", "Tenkan-sen period", "Indicators");

		_kijunPeriod = Param(nameof(KijunPeriod), 26)
		.SetGreaterThanZero()
		.SetDisplay("Kijun Period", "Kijun-sen period", "Indicators");

		_senkouSpanPeriod = Param(nameof(SenkouSpanPeriod), 52)
		.SetGreaterThanZero()
		.SetDisplay("Senkou Span Period", "Senkou Span B period", "Indicators");

		_takeProfitPct = Param(nameof(TakeProfitPct), 0m)
		.SetDisplay("Take Profit %", "Take profit percentage (0 - disabled)", "Risk Management");

		_stopLossPct = Param(nameof(StopLossPct), 0m)
		.SetDisplay("Stop Loss %", "Stop loss percentage (0 - disabled)", "Risk Management");

		_tradeDirection = Param(nameof(TradeDirection), "Long")
		.SetDisplay("Trading Mode", "Trade direction: Long or Short", "General");

		_entrySignalOptionsLong = Param(nameof(EntrySignalOptionsLong), "Bullish All")
		.SetDisplay("Entry Signal (Long)", "Entry signal filter for long mode", "Long Mode Signals");

		_exitSignalOptionsLong = Param(nameof(ExitSignalOptionsLong), "Bearish Weak")
		.SetDisplay("Exit Signal (Long)", "Exit signal filter for long mode", "Long Mode Signals");

		_entrySignalOptionsShort = Param(nameof(EntrySignalOptionsShort), "None")
		.SetDisplay("Entry Signal (Short)", "Entry signal filter for short mode", "Short Mode Signals");

		_exitSignalOptionsShort = Param(nameof(ExitSignalOptionsShort), "None")
		.SetDisplay("Exit Signal (Short)", "Exit signal filter for short mode", "Short Mode Signals");

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

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

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

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

		var ichimoku = new Ichimoku
		{
			Tenkan = { Length = TenkanPeriod },
			Kijun = { Length = KijunPeriod },
			SenkouB = { Length = SenkouSpanPeriod }
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(ichimoku, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawOwnTrades(area);
		}
	}

	private enum SignalStrengths
	{
		Strong,
		Neutral,
		Weak
	}

	private static bool IsSignalAllowed(string option, SignalStrengths strength, bool bullish)
	{
		if (option == "None")
			return false;

		if (bullish)
		{
			return option switch
			{
				"Bullish Strong" => strength == SignalStrengths.Strong,
				"Bullish Neutral" => strength == SignalStrengths.Neutral,
				"Bullish Weak" => strength == SignalStrengths.Weak,
				"Bullish Strong and Neutral" => strength is SignalStrengths.Strong or SignalStrengths.Neutral,
				"Bullish Neutral and Weak" => strength is SignalStrengths.Neutral or SignalStrengths.Weak,
				"Bullish Strong and Weak" => strength is SignalStrengths.Strong or SignalStrengths.Weak,
				"Bullish All" => true,
				_ => false
			};
		}

		return option switch
		{
			"Bearish Strong" => strength == SignalStrengths.Strong,
			"Bearish Neutral" => strength == SignalStrengths.Neutral,
			"Bearish Weak" => strength == SignalStrengths.Weak,
			"Bearish Strong and Neutral" => strength is SignalStrengths.Strong or SignalStrengths.Neutral,
			"Bearish Neutral and Weak" => strength is SignalStrengths.Neutral or SignalStrengths.Weak,
			"Bearish Strong and Weak" => strength is SignalStrengths.Strong or SignalStrengths.Weak,
			"Bearish All" => true,
			_ => false
		};
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (ichimokuValue is not IchimokuValue ichimoku)
			return;

		if (ichimoku.Tenkan is not decimal tenkan)
			return;

		if (ichimoku.Kijun is not decimal kijun)
			return;

		if (ichimoku.SenkouA is not decimal senkouA)
			return;

		if (ichimoku.SenkouB is not decimal senkouB)
			return;

		var upperCloud = Math.Max(senkouA, senkouB);
		var lowerCloud = Math.Min(senkouA, senkouB);

		var crossUp = tenkan > kijun && _prevTenkan <= _prevKijun;
		var crossDown = tenkan < kijun && _prevTenkan >= _prevKijun;

		if (crossUp)
		{
			var strength = tenkan > upperCloud ? SignalStrengths.Strong : tenkan < lowerCloud ? SignalStrengths.Weak : SignalStrengths.Neutral;

			if (TradeDirection == "Long" && IsSignalAllowed(EntrySignalOptionsLong, strength, true) && Position <= 0)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (TradeDirection == "Short" && IsSignalAllowed(ExitSignalOptionsShort, strength, true) && Position < 0)
			{
				BuyMarket();
			}
		}
		else if (crossDown)
		{
			var strength = tenkan < lowerCloud ? SignalStrengths.Strong : tenkan > upperCloud ? SignalStrengths.Weak : SignalStrengths.Neutral;

			if (TradeDirection == "Short" && IsSignalAllowed(EntrySignalOptionsShort, strength, false) && Position >= 0)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}
			else if (TradeDirection == "Long" && IsSignalAllowed(ExitSignalOptionsLong, strength, false) && Position > 0)
			{
				SellMarket();
			}
		}

		if (Position > 0)
		{
			if (TakeProfitPct > 0 && candle.ClosePrice >= _entryPrice * (1 + TakeProfitPct / 100m))
				SellMarket();
			else if (StopLossPct > 0 && candle.ClosePrice <= _entryPrice * (1 - StopLossPct / 100m))
				SellMarket();
		}
		else if (Position < 0)
		{
			if (TakeProfitPct > 0 && candle.ClosePrice <= _entryPrice * (1 - TakeProfitPct / 100m))
				BuyMarket();
			else if (StopLossPct > 0 && candle.ClosePrice >= _entryPrice * (1 + StopLossPct / 100m))
				BuyMarket();
		}

		_prevTenkan = tenkan;
		_prevKijun = kijun;
	}
}