Открыть на GitHub

Ишимоку и подразумеваемая волатильность

Стратегия Ichimoku Implied Volatility построена на анализе подразумеваемой волатильности с использованием индикаторов Ишимоку. Сигналы формируются, когда индикаторы подтверждают смену тренда на внутридневных данных (15м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров TenkanPeriod, KijunPeriod. Эти значения можно изменять для баланса риска и прибыли.

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

Подробности

  • Условия входа: см. реализацию для условий по индикаторам.
  • Длинные/короткие позиции: обе стороны.
  • Условия выхода: обратный сигнал или логика стопов.
  • Стопы: да, вычисляются на основе индикаторов.
  • Значения по умолчанию:
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouSpanBPeriod = 52
    • IVPeriod = 20
    • CandleType = TimeSpan.FromMinutes(15).TimeFrame()
  • Фильтры:
    • Категория: Следование за трендом
    • Направление: Оба
    • Индикаторы: multiple indicators
    • Стопы: Да
    • Сложность: Средняя
    • Таймфрейм: Внутридневной (15m)
    • Сезонность: Нет
    • Нейросети: Нет
    • Дивергенция: Нет
    • Уровень риска: Средний
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 with Implied Volatility strategy.
/// Entry condition:
/// Long: Price > Kumo && Tenkan > Kijun && IV > Avg(IV, N)
/// Short: Price < Kumo && Tenkan < Kijun && IV > Avg(IV, N)
/// Exit condition:
/// Long: Price < Kumo
/// Short: Price > Kumo
/// </summary>
public class IchimokuWithImpliedVolatilityStrategy : Strategy
{
	private static readonly object _ivSync = new();

	private readonly StrategyParam<int> _tenkanPeriod;
	private readonly StrategyParam<int> _kijunPeriod;
	private readonly StrategyParam<int> _senkouSpanBPeriod;
	private readonly StrategyParam<int> _ivPeriod;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private readonly Queue<decimal> _impliedVolatilityHistory = [];
	private decimal _avgImpliedVolatility;
	private decimal _impliedVolatilitySum;
	private decimal _currentImpliedVolatility;

	// Store previous indicator values for easier tracking
	private decimal _prevPrice;
	private bool _prevAboveKumo;
	private bool _prevTenkanAboveKijun;
	private int _cooldownRemaining;

	/// <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>
	/// Implied Volatility averaging period.
	/// </summary>
	public int IVPeriod
	{
		get => _ivPeriod.Value;
		set => _ivPeriod.Value = value;
	}

	/// <summary>
	/// Closed candles to wait before another position change.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Type of candles to use.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Constructor with default parameters.
	/// </summary>
	public IchimokuWithImpliedVolatilityStrategy()
	{
		_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
		.SetGreaterThanZero()
		.SetDisplay("Tenkan-Sen Period", "Tenkan-Sen (Conversion Line) period", "Ichimoku Settings")
		
		.SetOptimize(5, 13, 2);

		_kijunPeriod = Param(nameof(KijunPeriod), 26)
		.SetGreaterThanZero()
		.SetDisplay("Kijun-Sen Period", "Kijun-Sen (Base Line) period", "Ichimoku Settings")
		
		.SetOptimize(20, 30, 2);

		_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
		.SetGreaterThanZero()
		.SetDisplay("Senkou Span B Period", "Senkou Span B (2nd Leading Span) period", "Ichimoku Settings")
		
		.SetOptimize(40, 60, 4);

		_ivPeriod = Param(nameof(IVPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("IV Period", "Implied Volatility averaging period", "Volatility Settings")
		
		.SetOptimize(10, 30, 5);

		_cooldownBars = Param(nameof(CooldownBars), 24)
		.SetNotNegative()
		.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles to use", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		// reset stored values
		_impliedVolatilityHistory.Clear();

		_prevAboveKumo = default;
		_prevTenkanAboveKijun = default;
		_prevPrice = default;
		_avgImpliedVolatility = default;
		_impliedVolatilitySum = default;
		_currentImpliedVolatility = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

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

		// Subscribe to candles and bind indicator
		var subscription = SubscribeCandles(CandleType);

		subscription
		.BindEx(ichimoku, ProcessCandle)
		.Start();

		// Create chart visualization if available
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ichimoku);
			DrawOwnTrades(area);
		}

		// Enable position protection using Kijun-Sen as stop-loss
		StartProtection(
		new Unit(0), // No take profit
		new Unit(0)  // Dynamic stop-loss will be handled manually
		);
	}

	/// <summary>
	/// Process each candle and Ichimoku values.
	/// </summary>
	private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue)
	{
		// Skip unfinished candles
		if (candle.State != CandleStates.Finished)
		return;

		// Check if strategy is ready to trade
		if (!IsFormedAndOnlineAndAllowTrading())
		return;

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

		// Determine if price is above Kumo (cloud)
		var kumoTop = Math.Max(senkouA, senkouB);
		var kumoBottom = Math.Min(senkouA, senkouB);
		var priceAboveKumo = candle.ClosePrice > kumoTop;
		var priceBelowKumo = candle.ClosePrice < kumoBottom;

		// Check Tenkan/Kijun cross
		var tenkanAboveKijun = tenkan > kijun;

		// Update Implied Volatility (in a real system, this would come from market data)
		UpdateImpliedVolatility(candle);

		// Check IV condition
		var ivHigherThanAverage = GetImpliedVolatility() > _avgImpliedVolatility;

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		// First run, just store values
		if (_prevPrice == 0)
		{
			_prevPrice = candle.ClosePrice;
			_prevAboveKumo = priceAboveKumo;
			_prevTenkanAboveKijun = tenkanAboveKijun;
			return;
		}

		var bullishSetup = priceAboveKumo && tenkanAboveKijun && ivHigherThanAverage;
		var bearishSetup = priceBelowKumo && !tenkanAboveKijun && ivHigherThanAverage;
		var bullishTransition = bullishSetup && (!_prevAboveKumo || !_prevTenkanAboveKijun);
		var bearishTransition = bearishSetup && (_prevAboveKumo || _prevTenkanAboveKijun);

		// Long entry condition
		if (_cooldownRemaining == 0 && bullishTransition && Position <= 0)
		{
			LogInfo("Long signal: Price above Kumo, Tenkan above Kijun, IV elevated");
			BuyMarket(Volume + (Position < 0 ? Math.Abs(Position) : 0m));
			_cooldownRemaining = CooldownBars;
		}
		// Short entry condition
		else if (_cooldownRemaining == 0 && bearishTransition && Position >= 0)
		{
			LogInfo("Short signal: Price below Kumo, Tenkan below Kijun, IV elevated");
			SellMarket(Volume + (Position > 0 ? Math.Abs(Position) : 0m));
			_cooldownRemaining = CooldownBars;
		}

		// Exit conditions

		// Exit long if price falls below Kumo
		if (Position > 0 && !priceAboveKumo)
		{
			LogInfo("Exit long: Price fell below Kumo");
			SellMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}
		// Exit short if price rises above Kumo
		else if (Position < 0 && !priceBelowKumo)
		{
			LogInfo("Exit short: Price rose above Kumo");
			BuyMarket(Math.Abs(Position));
			_cooldownRemaining = CooldownBars;
		}

		// Use Kijun-Sen as trailing stop
		ApplyKijunAsStop(candle.ClosePrice, kijun);

		// Update previous values
		_prevPrice = candle.ClosePrice;
		_prevAboveKumo = priceAboveKumo;
		_prevTenkanAboveKijun = tenkanAboveKijun;
	}

	/// <summary>
	/// Update implied volatility value.
	/// In a real implementation, this would fetch data from market.
	/// </summary>
	private void UpdateImpliedVolatility(ICandleMessage candle)
	{
		lock (_ivSync)
		{
			// Simple IV simulation based on candle's high-low range
			// In reality, this would come from option pricing data
			decimal iv = (candle.HighPrice - candle.LowPrice) / candle.OpenPrice * 100;

			// Add to history and maintain history length
			_currentImpliedVolatility = iv;
			_impliedVolatilityHistory.Enqueue(iv);
			_impliedVolatilitySum += iv;

			if (_impliedVolatilityHistory.Count > IVPeriod)
			{
				_impliedVolatilitySum -= _impliedVolatilityHistory.Dequeue();
			}

			_avgImpliedVolatility = _impliedVolatilityHistory.Count > 0
				? _impliedVolatilitySum / _impliedVolatilityHistory.Count
				: 0;

			LogInfo($"IV: {iv}, Avg IV: {_avgImpliedVolatility}");
		}
	}

	/// <summary>
	/// Get current implied volatility.
	/// </summary>
	private decimal GetImpliedVolatility()
	{
		return _currentImpliedVolatility;
	}

	/// <summary>
	/// Use Kijun-Sen as a trailing stop level.
	/// </summary>
	private void ApplyKijunAsStop(decimal price, decimal kijun)
	{
		// Long position: exit if price drops below Kijun
		if (Position > 0 && price < kijun)
		{
			LogInfo("Kijun-Sen stop triggered for long position");
			SellMarket(Math.Abs(Position));
		}
		// Short position: exit if price rises above Kijun
		else if (Position < 0 && price > kijun)
		{
			LogInfo("Kijun-Sen stop triggered for short position");
			BuyMarket(Math.Abs(Position));
		}
	}
}