GitHub で見る

Ichimoku Implied Volatility

The Ichimoku Implied Volatility strategy is built around Ichimoku Implied Volatility.

Testing indicates an average annual return of about 109%. It performs best in the crypto market.

Signals trigger when its indicators confirms trend changes on intraday (15m) data. This makes the method suitable for active traders.

Stops rely on ATR multiples and factors like TenkanPeriod, KijunPeriod. Adjust these defaults to balance risk and reward.

Details

  • Entry Criteria: see implementation for indicator conditions.
  • Long/Short: Both directions.
  • Exit Criteria: opposite signal or stop logic.
  • Stops: Yes, using indicator-based calculations.
  • Default Values:
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouSpanBPeriod = 52
    • IVPeriod = 20
    • CandleType = TimeSpan.FromMinutes(15).TimeFrame()
  • Filters:
    • Category: Trend following
    • Direction: Both
    • Indicators: multiple indicators
    • Stops: Yes
    • Complexity: Intermediate
    • Timeframe: Intraday (15m)
    • Seasonality: No
    • Neural Networks: No
    • Divergence: No
    • Risk Level: Medium
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));
		}
	}
}