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.FromMinutes(5).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")
clr.AddReference("StockSharp.BusinessEntities")
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.FromMinutes(5))) \
.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()