在 GitHub 上查看

Ichimoku Implied Volatility

Ichimoku Implied Volatility 策略基于 Ichimoku Implied Volatility。

测试表明年均收益约为 109%,该策略在加密市场表现最佳。

当 its indicators confirms trend changes 在日内(15m)数据上得到确认时触发信号,适合积极交易者。

止损依赖于 ATR 倍数以及 TenkanPeriod, KijunPeriod 等参数,可根据需要调整以平衡风险与收益。

详情

  • 入场条件:参见指标条件实现.
  • 多空方向:双向.
  • 退出条件:反向信号或止损逻辑.
  • 止损:是,基于指标计算.
  • 默认值:
    • 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));
		}
	}
}