Ichimoku Implied Volatility
Ichimoku Implied Volatility 策略基于 Ichimoku Implied Volatility。
测试表明年均收益约为 109%,该策略在加密市场表现最佳。
当 its indicators confirms trend changes 在日内(15m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 TenkanPeriod, KijunPeriod 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
TenkanPeriod = 9KijunPeriod = 26SenkouSpanBPeriod = 52IVPeriod = 20CandleType = 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));
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from System.Collections.Generic import Queue
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Ichimoku
from StockSharp.Algo.Strategies import Strategy
class ichimoku_implied_volatility_strategy(Strategy):
"""
Ichimoku with Implied Volatility strategy.
"""
def __init__(self):
super(ichimoku_implied_volatility_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan-Sen Period", "Tenkan-Sen (Conversion Line) period", "Ichimoku Settings")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun-Sen Period", "Kijun-Sen (Base Line) period", "Ichimoku Settings")
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Senkou Span B (2nd Leading Span) period", "Ichimoku Settings")
self._iv_period = self.Param("IVPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("IV Period", "Implied Volatility averaging period", "Volatility Settings")
self._cooldown_bars = self.Param("CooldownBars", 24) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Closed candles to wait before another position change", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._iv_history = []
self._avg_iv = 0.0
self._iv_sum = 0.0
self._current_iv = 0.0
self._prev_price = 0.0
self._prev_above_kumo = False
self._prev_tenkan_above_kijun = False
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(ichimoku_implied_volatility_strategy, self).OnReseted()
self._iv_history = []
self._prev_above_kumo = False
self._prev_tenkan_above_kijun = False
self._prev_price = 0.0
self._avg_iv = 0.0
self._iv_sum = 0.0
self._current_iv = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ichimoku_implied_volatility_strategy, self).OnStarted2(time)
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = int(self._tenkan_period.Value)
ichimoku.Kijun.Length = int(self._kijun_period.Value)
ichimoku.SenkouB.Length = int(self._senkou_span_b_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ichimoku, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(0),
Unit(0)
)
def ProcessCandle(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
tenkan_val = ichimoku_value.Tenkan
if tenkan_val is None:
return
tenkan = float(tenkan_val)
kijun_val = ichimoku_value.Kijun
if kijun_val is None:
return
kijun = float(kijun_val)
senkou_a_val = ichimoku_value.SenkouA
if senkou_a_val is None:
return
senkou_a = float(senkou_a_val)
senkou_b_val = ichimoku_value.SenkouB
if senkou_b_val is None:
return
senkou_b = float(senkou_b_val)
kumo_top = max(senkou_a, senkou_b)
kumo_bottom = min(senkou_a, senkou_b)
close = float(candle.ClosePrice)
price_above_kumo = close > kumo_top
price_below_kumo = close < kumo_bottom
tenkan_above_kijun = tenkan > kijun
self.UpdateImpliedVolatility(candle)
iv_higher = self._current_iv > self._avg_iv
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
iv_period = int(self._iv_period.Value)
cooldown = int(self._cooldown_bars.Value)
if self._prev_price == 0.0:
self._prev_price = close
self._prev_above_kumo = price_above_kumo
self._prev_tenkan_above_kijun = tenkan_above_kijun
return
bullish_setup = price_above_kumo and tenkan_above_kijun and iv_higher
bearish_setup = price_below_kumo and (not tenkan_above_kijun) and iv_higher
bullish_transition = bullish_setup and ((not self._prev_above_kumo) or (not self._prev_tenkan_above_kijun))
bearish_transition = bearish_setup and (self._prev_above_kumo or self._prev_tenkan_above_kijun)
if self._cooldown_remaining == 0 and bullish_transition and self.Position <= 0:
vol = self.Volume
if self.Position < 0:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._cooldown_remaining = cooldown
elif self._cooldown_remaining == 0 and bearish_transition and self.Position >= 0:
vol = self.Volume
if self.Position > 0:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._cooldown_remaining = cooldown
if self.Position > 0 and not price_above_kumo:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and not price_below_kumo:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self.ApplyKijunAsStop(candle.ClosePrice, kijun)
self._prev_price = close
self._prev_above_kumo = price_above_kumo
self._prev_tenkan_above_kijun = tenkan_above_kijun
def UpdateImpliedVolatility(self, candle):
iv = float((candle.HighPrice - candle.LowPrice) / candle.OpenPrice * 100)
self._current_iv = iv
self._iv_history.append(iv)
self._iv_sum += iv
iv_period = int(self._iv_period.Value)
if len(self._iv_history) > iv_period:
self._iv_sum -= self._iv_history.pop(0)
if len(self._iv_history) > 0:
self._avg_iv = self._iv_sum / len(self._iv_history)
else:
self._avg_iv = 0.0
self.LogInfo("IV: {0}, Avg IV: {1}".format(iv, self._avg_iv))
def ApplyKijunAsStop(self, price, kijun):
if self.Position > 0 and float(price) < kijun:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0 and float(price) > kijun:
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
return ichimoku_implied_volatility_strategy()