Ишимоку и подразумеваемая волатильность
Стратегия Ichimoku Implied Volatility построена на анализе подразумеваемой волатильности с использованием индикаторов Ишимоку. Сигналы формируются, когда индикаторы подтверждают смену тренда на внутридневных данных (15м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров TenkanPeriod, KijunPeriod. Эти значения можно изменять для баланса риска и прибыли.
Тестирование показывает среднегодичную доходность около 109%. Стратегию лучше запускать на крипторынке.
Подробности
- Условия входа: см. реализацию для условий по индикаторам.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: обратный сигнал или логика стопов.
- Стопы: да, вычисляются на основе индикаторов.
- Значения по умолчанию:
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()