Ichimoku Volatility Contraction
Ichimoku Volatility Contraction 策略基于 Ichimoku Volatility Contraction。
测试表明年均收益约为 85%,该策略在加密市场表现最佳。
当 its indicators confirms volatility contraction patterns 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 TenkanPeriod, KijunPeriod 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
TenkanPeriod = 9KijunPeriod = 26SenkouSpanBPeriod = 52AtrPeriod = 14DeviationFactor = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: multiple indicators
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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 Volatility Contraction strategy.
/// Enters positions when Ichimoku signals a trend and volatility is contracting.
/// </summary>
public class IchimokuVolatilityContractionStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _deviationFactor;
private readonly StrategyParam<DataType> _candleType;
private static readonly object _sync = new();
private decimal _avgAtr;
private decimal _atrStdDev;
private int _processedCandles;
/// <summary>
/// Tenkan-sen (Conversion Line) period.
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Kijun-sen (Base Line) period.
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Senkou Span B (Leading Span B) period.
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// ATR period for volatility calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Deviation factor for volatility contraction detection.
/// </summary>
public decimal DeviationFactor
{
get => _deviationFactor.Value;
set => _deviationFactor.Value = value;
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize strategy.
/// </summary>
public IchimokuVolatilityContractionStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Period for Tenkan-sen (Conversion Line)", "Ichimoku Settings")
.SetOptimize(7, 11, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Period for Kijun-sen (Base Line)", "Ichimoku Settings")
.SetOptimize(20, 30, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (Leading Span B)", "Ichimoku Settings")
.SetOptimize(40, 60, 4);
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for Average True Range calculation", "Volatility Settings")
.SetOptimize(10, 20, 2);
_deviationFactor = Param(nameof(DeviationFactor), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Deviation Factor", "Factor multiplied by standard deviation to detect volatility contraction", "Volatility Settings")
.SetOptimize(1.5m, 3.0m, 0.5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).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();
_avgAtr = 0;
_atrStdDev = 0;
_processedCandles = 0;
}
/// <inheritdoc />
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 }
};
// Create ATR indicator for volatility measurement
var atr = new AverageTrueRange
{
Length = AtrPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(candle => ProcessCandle(candle, ichimoku, atr))
.Start();
// Start position protection
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ichimoku);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, Ichimoku ichimoku, AverageTrueRange atr)
{
if (candle.State != CandleStates.Finished)
return;
lock (_sync)
{
var ichimokuValue = ichimoku.Process(new CandleIndicatorValue(ichimoku, candle) { IsFinal = true });
var atrValue = atr.Process(new CandleIndicatorValue(atr, candle) { IsFinal = true });
if (!ichimokuValue.IsFinal || !atrValue.IsFinal || !ichimoku.IsFormed || !atr.IsFormed)
return;
var currentAtr = atrValue.ToDecimal();
_processedCandles++;
if (_processedCandles == 1)
{
_avgAtr = currentAtr;
_atrStdDev = 0m;
}
else
{
var alpha = 2.0m / (AtrPeriod + 1);
var oldAvg = _avgAtr;
_avgAtr = alpha * currentAtr + (1 - alpha) * _avgAtr;
var atrDev = Math.Abs(currentAtr - oldAvg);
_atrStdDev = alpha * atrDev + (1 - alpha) * _atrStdDev;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (ichimokuValue is not IchimokuValue ichimokuTyped ||
ichimokuTyped.Tenkan is not decimal tenkan ||
ichimokuTyped.Kijun is not decimal kijun ||
ichimokuTyped.SenkouA is not decimal senkouA ||
ichimokuTyped.SenkouB is not decimal senkouB)
{
return;
}
var upperKumo = Math.Max(senkouA, senkouB);
var lowerKumo = Math.Min(senkouA, senkouB);
var isVolatilityContraction = currentAtr <= _avgAtr;
if (isVolatilityContraction)
{
if (candle.ClosePrice > upperKumo && tenkan > kijun && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (candle.ClosePrice < lowerKumo && tenkan < kijun && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
if (Position > 0 && candle.ClosePrice < lowerKumo)
SellMarket(Position);
else if (Position < 0 && candle.ClosePrice > upperKumo)
BuyMarket(-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 StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import Ichimoku, AverageTrueRange, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class ichimoku_volatility_contraction_strategy(Strategy):
"""
Ichimoku with Volatility Contraction strategy.
Enters positions when Ichimoku signals a trend and volatility is contracting.
"""
def __init__(self):
super(ichimoku_volatility_contraction_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan Period", "Period for Tenkan-sen (Conversion Line)", "Ichimoku Settings") \
.SetCanOptimize(True) \
.SetOptimize(7, 11, 1)
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun Period", "Period for Kijun-sen (Base Line)", "Ichimoku Settings") \
.SetCanOptimize(True) \
.SetOptimize(20, 30, 2)
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (Leading Span B)", "Ichimoku Settings") \
.SetCanOptimize(True) \
.SetOptimize(40, 60, 4)
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period for Average True Range calculation", "Volatility Settings") \
.SetCanOptimize(True) \
.SetOptimize(10, 20, 2)
self._deviation_factor = self.Param("DeviationFactor", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Deviation Factor", "Factor multiplied by standard deviation to detect volatility contraction", "Volatility Settings") \
.SetCanOptimize(True) \
.SetOptimize(1.5, 3.0, 0.5)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._avg_atr = 0.0
self._atr_std_dev = 0.0
self._processed_candles = 0
self._ichimoku = None
self._atr = None
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(ichimoku_volatility_contraction_strategy, self).OnReseted()
self._avg_atr = 0.0
self._atr_std_dev = 0.0
self._processed_candles = 0
self._ichimoku = None
self._atr = None
def OnStarted2(self, time):
super(ichimoku_volatility_contraction_strategy, self).OnStarted2(time)
self._ichimoku = Ichimoku()
self._ichimoku.Tenkan.Length = int(self._tenkan_period.Value)
self._ichimoku.Kijun.Length = int(self._kijun_period.Value)
self._ichimoku.SenkouB.Length = int(self._senkou_span_b_period.Value)
self._atr = AverageTrueRange()
self._atr.Length = int(self._atr_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ichimoku)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
ich_iv = CandleIndicatorValue(self._ichimoku, candle)
ich_iv.IsFinal = True
ichimoku_value = self._ichimoku.Process(ich_iv)
atr_iv = CandleIndicatorValue(self._atr, candle)
atr_iv.IsFinal = True
atr_value = self._atr.Process(atr_iv)
if not ichimoku_value.IsFinal or not atr_value.IsFinal or not self._ichimoku.IsFormed or not self._atr.IsFormed:
return
current_atr = float(atr_value)
self._processed_candles += 1
atr_period = int(self._atr_period.Value)
if self._processed_candles == 1:
self._avg_atr = current_atr
self._atr_std_dev = 0.0
else:
alpha = 2.0 / (atr_period + 1)
old_avg = self._avg_atr
self._avg_atr = alpha * current_atr + (1.0 - alpha) * self._avg_atr
atr_dev = abs(current_atr - old_avg)
self._atr_std_dev = alpha * atr_dev + (1.0 - alpha) * self._atr_std_dev
if not self.IsFormedAndOnlineAndAllowTrading():
return
if ichimoku_value.Tenkan is None or ichimoku_value.Kijun is None or \
ichimoku_value.SenkouA is None or ichimoku_value.SenkouB is None:
return
tenkan = float(ichimoku_value.Tenkan)
kijun = float(ichimoku_value.Kijun)
senkou_a = float(ichimoku_value.SenkouA)
senkou_b = float(ichimoku_value.SenkouB)
upper_kumo = max(senkou_a, senkou_b)
lower_kumo = min(senkou_a, senkou_b)
is_volatility_contraction = current_atr <= self._avg_atr
close_price = float(candle.ClosePrice)
if is_volatility_contraction:
if close_price > upper_kumo and tenkan > kijun and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif close_price < lower_kumo and tenkan < kijun and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
if self.Position > 0 and close_price < lower_kumo:
self.SellMarket(self.Position)
elif self.Position < 0 and close_price > upper_kumo:
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
return ichimoku_volatility_contraction_strategy()