Ichimoku Adx Strategy
本策略结合一目均衡表和ADX指标。做多条件:价格在云层上方,且转折线高于基准线,ADX>25;做空条件:价格在云层下方,且转折线低于基准线,ADX>25。价格穿越云层相反方向时平仓。
测试表明年均收益约为 187%,该策略在股票市场表现最佳。
策略利用云图信号配合ADX过滤强势趋势。当价格突破云层并得到ADX确认时进场。适合偏好结构化趋势形态的交易者,ATR设定的止损帮助控制风险。
细节
- 入场条件:
- 多头:
Price > Cloud && Tenkan > Kijun && ADX > AdxThreshold - 空头:
Price < Cloud && Tenkan < Kijun && ADX > AdxThreshold
- 多头:
- 多/空: 双向
- 离场条件: 价格反向穿越云层
- 止损: 使用云图作为跟踪止损
- 默认值:
TenkanPeriod= 9KijunPeriod= 26SenkouSpanBPeriod= 52AdxPeriod= 14AdxThreshold= 25mCandleType= TimeSpan.FromMinutes(15).TimeFrame()
- 过滤器:
- 类别: Trend
- 方向: 双向
- 指标: Ichimoku Cloud, ADX
- 止损: 是
- 复杂度: 中等
- 时间框架: 中期
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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>
/// Strategy based on Ichimoku Cloud and ADX indicators.
///
/// Entry criteria:
/// Long: Price > Kumo (cloud) && Tenkan > Kijun && ADX > 25 (uptrend with strong movement)
/// Short: Price < Kumo (cloud) && Tenkan < Kijun && ADX > 25 (downtrend with strong movement)
///
/// Exit criteria:
/// Long: Price < Kumo (price falls below cloud)
/// Short: Price > Kumo (price rises above cloud)
/// </summary>
public class IchimokuAdxStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
// Previous state tracking
private bool _isPriceAboveCloud;
private bool _isTenkanAboveKijun;
private decimal _lastAdxValue;
/// <summary>
/// Period for Tenkan-sen calculation (conversion line).
/// </summary>
public int TenkanPeriod
{
get => _tenkanPeriod.Value;
set => _tenkanPeriod.Value = value;
}
/// <summary>
/// Period for Kijun-sen calculation (base line).
/// </summary>
public int KijunPeriod
{
get => _kijunPeriod.Value;
set => _kijunPeriod.Value = value;
}
/// <summary>
/// Period for Senkou Span B calculation (second cloud component).
/// </summary>
public int SenkouSpanBPeriod
{
get => _senkouSpanBPeriod.Value;
set => _senkouSpanBPeriod.Value = value;
}
/// <summary>
/// Period for ADX calculation.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Threshold for ADX to confirm trend strength.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public IchimokuAdxStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Tenkan Period", "Period for Tenkan-sen (conversion line)", "Ichimoku")
.SetOptimize(7, 13, 2);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Kijun Period", "Period for Kijun-sen (base line)", "Ichimoku")
.SetOptimize(20, 32, 3);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (second cloud component)", "Ichimoku")
.SetOptimize(40, 60, 5);
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
.SetOptimize(10, 20, 5);
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "Minimum ADX value to confirm trend strength", "Indicators")
.SetOptimize(20m, 30m, 5m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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();
_isPriceAboveCloud = default;
_isTenkanAboveKijun = default;
_lastAdxValue = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
// Create subscription
var subscription = SubscribeCandles(CandleType);
// We'll need to manually bind Ichimoku and ADX separately as they have different output values
subscription
.BindEx(ichimoku, adx, ProcessIndicators)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ichimoku);
// Create separate area for ADX
var adxArea = CreateChartArea();
if (adxArea != null)
{
DrawIndicator(adxArea, adx);
}
DrawOwnTrades(area);
}
}
// Process Ichimoku indicator data
private void ProcessIndicators(ICandleMessage candle, IIndicatorValue ichimokuValue, IIndicatorValue adxValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
var typedAdx = (AverageDirectionalIndexValue)adxValue;
if (typedAdx.MovingAverage is not decimal adx)
return;
_lastAdxValue = adx;
// Get Ichimoku values
// The component values must be extracted based on the Ichimoku implementation
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 cloud boundaries
var cloudTop = Math.Max(senkouA, senkouB);
var cloudBottom = Math.Min(senkouA, senkouB);
// Update state
var isPriceAboveCloud = candle.ClosePrice > cloudTop;
var isPriceBelowCloud = candle.ClosePrice < cloudBottom;
var isTenkanAboveKijun = tenkan > kijun;
// Log current state
LogInfo($"Close: {candle.ClosePrice}, Tenkan: {tenkan:N2}, Kijun: {kijun:N2}, " +
$"Cloud Top: {cloudTop:N2}, Cloud Bottom: {cloudBottom:N2}, ADX: {_lastAdxValue:N2}");
var isPriceRelativeToCloudChanged = _isPriceAboveCloud != isPriceAboveCloud;
// Only make trading decisions if both Ichimoku and ADX have been calculated
if (_lastAdxValue <= 0)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
var isStrongTrend = _lastAdxValue > AdxThreshold;
// Trading logic
if (Position == 0) // No position
{
if (isPriceAboveCloud && isTenkanAboveKijun && isStrongTrend)
{
// Buy signal: price above cloud, Tenkan above Kijun, strong trend
BuyMarket(Volume);
LogInfo($"Buy signal: Price above cloud, Tenkan above Kijun, ADX = {_lastAdxValue}");
}
else if (isPriceBelowCloud && !isTenkanAboveKijun && isStrongTrend)
{
// Sell signal: price below cloud, Tenkan below Kijun, strong trend
SellMarket(Volume);
LogInfo($"Sell signal: Price below cloud, Tenkan below Kijun, ADX = {_lastAdxValue}");
}
}
else if (isPriceRelativeToCloudChanged) // Exit on cloud crossing
{
if (Position > 0 && !isPriceAboveCloud)
{
// Exit long position: price fell below cloud
SellMarket(Math.Abs(Position));
LogInfo($"Exit long position: Price fell into/below cloud");
}
else if (Position < 0 && !isPriceBelowCloud)
{
// Exit short position: price rose above cloud
BuyMarket(Math.Abs(Position));
LogInfo($"Exit short position: Price rose into/above cloud");
}
}
// Update tracking variables
_isPriceAboveCloud = isPriceAboveCloud;
_isTenkanAboveKijun = isTenkanAboveKijun;
}
}
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, CandleStates
from StockSharp.Algo.Indicators import Ichimoku, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class ichimoku_adx_strategy(Strategy):
"""
Strategy based on Ichimoku Cloud and ADX indicators.
Entry criteria:
Long: Price > Kumo (cloud) && Tenkan > Kijun && ADX > 25 (uptrend with strong movement)
Short: Price < Kumo (cloud) && Tenkan < Kijun && ADX > 25 (downtrend with strong movement)
Exit criteria:
Long: Price < Kumo (price falls below cloud)
Short: Price > Kumo (price rises above cloud)
"""
def __init__(self):
super(ichimoku_adx_strategy, self).__init__()
# Period for Tenkan-sen calculation (conversion line).
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetGreaterThanZero() \
.SetDisplay("Tenkan Period", "Period for Tenkan-sen (conversion line)", "Ichimoku") \
.SetCanOptimize(True) \
.SetOptimize(7, 13, 2)
# Period for Kijun-sen calculation (base line).
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetGreaterThanZero() \
.SetDisplay("Kijun Period", "Period for Kijun-sen (base line)", "Ichimoku") \
.SetCanOptimize(True) \
.SetOptimize(20, 32, 3)
# Period for Senkou Span B calculation (second cloud component).
self._senkou_span_b_period = self.Param("SenkouSpanBPeriod", 52) \
.SetGreaterThanZero() \
.SetDisplay("Senkou Span B Period", "Period for Senkou Span B (second cloud component)", "Ichimoku") \
.SetCanOptimize(True) \
.SetOptimize(40, 60, 5)
# Period for ADX calculation.
self._adx_period = self.Param("AdxPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(10, 20, 5)
# Threshold for ADX to confirm trend strength.
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetGreaterThanZero() \
.SetDisplay("ADX Threshold", "Minimum ADX value to confirm trend strength", "Indicators") \
.SetCanOptimize(True) \
.SetOptimize(20.0, 30.0, 5.0)
# Type of candles to use.
self._candle_type = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
# Previous state tracking
self._is_price_above_cloud = False
self._is_tenkan_above_kijun = False
self._last_adx_value = 0.0
@property
def TenkanPeriod(self):
"""Period for Tenkan-sen calculation (conversion line)."""
return self._tenkan_period.Value
@TenkanPeriod.setter
def TenkanPeriod(self, value):
self._tenkan_period.Value = value
@property
def KijunPeriod(self):
"""Period for Kijun-sen calculation (base line)."""
return self._kijun_period.Value
@KijunPeriod.setter
def KijunPeriod(self, value):
self._kijun_period.Value = value
@property
def SenkouSpanBPeriod(self):
"""Period for Senkou Span B calculation (second cloud component)."""
return self._senkou_span_b_period.Value
@SenkouSpanBPeriod.setter
def SenkouSpanBPeriod(self, value):
self._senkou_span_b_period.Value = value
@property
def AdxPeriod(self):
"""Period for ADX calculation."""
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def AdxThreshold(self):
"""Threshold for ADX to confirm trend strength."""
return self._adx_threshold.Value
@AdxThreshold.setter
def AdxThreshold(self, value):
self._adx_threshold.Value = value
@property
def CandleType(self):
"""Type of candles to use."""
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(ichimoku_adx_strategy, self).OnReseted()
self._is_price_above_cloud = False
self._is_tenkan_above_kijun = False
self._last_adx_value = 0.0
def OnStarted2(self, time):
super(ichimoku_adx_strategy, self).OnStarted2(time)
# Create indicators
ichimoku = Ichimoku()
ichimoku.Tenkan.Length = self.TenkanPeriod
ichimoku.Kijun.Length = self.KijunPeriod
ichimoku.SenkouB.Length = self.SenkouSpanBPeriod
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
# Create subscription
subscription = self.SubscribeCandles(self.CandleType)
# We'll need to manually bind Ichimoku and ADX separately as they have different output values
subscription.BindEx(ichimoku, adx, self.ProcessIndicators).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
# Create separate area for ADX
adx_area = self.CreateChartArea()
if adx_area is not None:
self.DrawIndicator(adx_area, adx)
self.DrawOwnTrades(area)
# Process Ichimoku indicator data
def ProcessIndicators(self, candle, ichimoku_value, adx_value):
# Skip unfinished candles
if candle.State != CandleStates.Finished:
return
if adx_value.MovingAverage is None:
return
adx = float(adx_value.MovingAverage)
self._last_adx_value = adx
# Get Ichimoku values
if ichimoku_value.Tenkan is None:
return
tenkan = float(ichimoku_value.Tenkan)
if ichimoku_value.Kijun is None:
return
kijun = float(ichimoku_value.Kijun)
if ichimoku_value.SenkouA is None:
return
senkou_a = float(ichimoku_value.SenkouA)
if ichimoku_value.SenkouB is None:
return
senkou_b = float(ichimoku_value.SenkouB)
# Determine cloud boundaries
cloud_top = Math.Max(senkou_a, senkou_b)
cloud_bottom = Math.Min(senkou_a, senkou_b)
# Update state
is_price_above_cloud = candle.ClosePrice > cloud_top
is_price_below_cloud = candle.ClosePrice < cloud_bottom
is_tenkan_above_kijun = tenkan > kijun
# Log current state
self.LogInfo(
"Close: {0}, Tenkan: {1:.2f}, Kijun: {2:.2f}, Cloud Top: {3:.2f}, Cloud Bottom: {4:.2f}, ADX: {5:.2f}".format(
candle.ClosePrice, tenkan, kijun, cloud_top, cloud_bottom, self._last_adx_value))
is_price_relative_to_cloud_changed = self._is_price_above_cloud != is_price_above_cloud
# Only make trading decisions if both Ichimoku and ADX have been calculated
if self._last_adx_value <= 0:
return
# Check if strategy is ready to trade
is_strong_trend = self._last_adx_value > self.AdxThreshold
# Trading logic
if self.Position == 0: # No position
if is_price_above_cloud and is_tenkan_above_kijun and is_strong_trend:
# Buy signal: price above cloud, Tenkan above Kijun, strong trend
self.BuyMarket(self.Volume)
self.LogInfo("Buy signal: Price above cloud, Tenkan above Kijun, ADX = {0}".format(self._last_adx_value))
elif is_price_below_cloud and not is_tenkan_above_kijun and is_strong_trend:
# Sell signal: price below cloud, Tenkan below Kijun, strong trend
self.SellMarket(self.Volume)
self.LogInfo("Sell signal: Price below cloud, Tenkan below Kijun, ADX = {0}".format(self._last_adx_value))
elif is_price_relative_to_cloud_changed: # Exit on cloud crossing
if self.Position > 0 and not is_price_above_cloud:
# Exit long position: price fell below cloud
self.SellMarket(Math.Abs(self.Position))
self.LogInfo("Exit long position: Price fell into/below cloud")
elif self.Position < 0 and not is_price_below_cloud:
# Exit short position: price rose above cloud
self.BuyMarket(Math.Abs(self.Position))
self.LogInfo("Exit short position: Price rose into/above cloud")
# Update tracking variables
self._is_price_above_cloud = is_price_above_cloud
self._is_tenkan_above_kijun = is_tenkan_above_kijun
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return ichimoku_adx_strategy()