Ишимоку и показатель Херста
Стратегия Ichimoku Hurst Exponent основана на индикаторе Ichimoku Kinko Hyo с фильтром по экспоненте Херста. Сигналы формируются, когда показатель Херста подтверждает смену тренда на внутридневных данных (15м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров TenkanPeriod, KijunPeriod. Эти значения можно изменить для баланса риска и прибыли.
Тестирование показывает среднегодичную доходность около 64%. Стратегию лучше запускать на рынке Форекс.
Подробности
- Условия входа: см. реализацию для условий по индикаторам.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: обратный сигнал или логика стопов.
- Стопы: да, вычисляются на основе индикаторов.
- Значения по умолчанию:
TenkanPeriod = 9KijunPeriod = 26SenkouSpanBPeriod = 52HurstPeriod = 100HurstThreshold = 0.5mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: Hurst, Exponent
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (15m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
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 Kinko Hyo indicator with Hurst exponent filter.
/// </summary>
public class IchimokuHurstStrategy : Strategy
{
private readonly StrategyParam<int> _tenkanPeriod;
private readonly StrategyParam<int> _kijunPeriod;
private readonly StrategyParam<int> _senkouSpanBPeriod;
private readonly StrategyParam<int> _hurstPeriod;
private readonly StrategyParam<decimal> _hurstThreshold;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _signalCooldownBars;
private Ichimoku _ichimoku;
// Data for Hurst exponent calculations
private readonly List<decimal> _prices = [];
private decimal _hurstExponent;
private decimal? _prevTenkan;
private decimal? _prevKijun;
private int _cooldownRemaining;
/// <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>
/// Hurst exponent calculation period.
/// </summary>
public int HurstPeriod
{
get => _hurstPeriod.Value;
set => _hurstPeriod.Value = value;
}
/// <summary>
/// Hurst exponent threshold for trend strength.
/// </summary>
public decimal HurstThreshold
{
get => _hurstThreshold.Value;
set => _hurstThreshold.Value = value;
}
/// <summary>
/// Candle type to use for the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Bars to wait between position changes.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="IchimokuHurstStrategy"/>.
/// </summary>
public IchimokuHurstStrategy()
{
_tenkanPeriod = Param(nameof(TenkanPeriod), 9)
.SetDisplay("Tenkan Period", "Tenkan-sen (conversion line) period", "Ichimoku")
.SetOptimize(5, 15, 1);
_kijunPeriod = Param(nameof(KijunPeriod), 26)
.SetDisplay("Kijun Period", "Kijun-sen (base line) period", "Ichimoku")
.SetOptimize(20, 40, 2);
_senkouSpanBPeriod = Param(nameof(SenkouSpanBPeriod), 52)
.SetDisplay("Senkou Span B Period", "Senkou Span B (leading span B) period", "Ichimoku")
.SetOptimize(40, 70, 5);
_hurstPeriod = Param(nameof(HurstPeriod), 100)
.SetDisplay("Hurst Period", "Hurst exponent calculation period", "Hurst Exponent")
.SetOptimize(50, 200, 10);
_hurstThreshold = Param(nameof(HurstThreshold), 0.5m)
.SetDisplay("Hurst Threshold", "Hurst exponent threshold for trend strength", "Hurst Exponent")
.SetOptimize(0.45m, 0.6m, 0.05m);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_hurstExponent = 0.5m; // Default Hurst exponent value
_prevTenkan = null;
_prevKijun = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create Ichimoku indicator
_ichimoku = new Ichimoku
{
Tenkan = { Length = TenkanPeriod },
Kijun = { Length = KijunPeriod },
SenkouB = { Length = SenkouSpanBPeriod }
};
// Create subscription and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_ichimoku, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ichimoku);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue ichimokuValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Store 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;
// Update price data for Hurst exponent calculation
_prices.Add(candle.ClosePrice);
// Keep only the number of prices needed for Hurst calculation
while (_prices.Count > HurstPeriod)
_prices.RemoveAt(0);
// Calculate Hurst exponent when we have enough data
if (_prices.Count >= HurstPeriod)
CalculateHurstExponent();
// Continue with position checks
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
// Check if price is above/below Kumo (cloud)
var isPriceAboveKumo = candle.ClosePrice > Math.Max(senkouA, senkouB);
var isPriceBelowKumo = candle.ClosePrice < Math.Min(senkouA, senkouB);
if (_prevTenkan is decimal previousTenkan && _prevKijun is decimal previousKijun)
{
var crossUp = previousTenkan <= previousKijun && tenkan > kijun;
var crossDown = previousTenkan >= previousKijun && tenkan < kijun;
var longExit = Position > 0 && (isPriceBelowKumo || crossDown);
var shortExit = Position < 0 && (isPriceAboveKumo || crossUp);
if (longExit)
{
SellMarket(Position);
_cooldownRemaining = SignalCooldownBars;
}
else if (shortExit)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && isPriceAboveKumo && crossUp && _hurstExponent > HurstThreshold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
else if (_cooldownRemaining == 0 && isPriceBelowKumo && crossDown && _hurstExponent > HurstThreshold && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_cooldownRemaining = SignalCooldownBars;
}
}
_prevTenkan = tenkan;
_prevKijun = kijun;
}
private void CalculateHurstExponent()
{
// This is a simplified Hurst exponent calculation using R/S analysis
// Note: A full implementation would use multiple time scales
// Calculate log returns
List<decimal> logReturns = [];
for (int i = 1; i < _prices.Count; i++)
{
if (_prices[i-1] != 0)
logReturns.Add((decimal)Math.Log((double)(_prices[i] / _prices[i-1])));
}
if (logReturns.Count < 10)
return;
// Calculate mean
decimal mean = logReturns.Sum() / logReturns.Count;
// Calculate cumulative deviation series
List<decimal> cumulativeDeviation = [];
decimal sum = 0;
foreach (var logReturn in logReturns)
{
sum += (logReturn - mean);
cumulativeDeviation.Add(sum);
}
// Calculate range (max - min of cumulative deviation)
decimal range = cumulativeDeviation.Max() - cumulativeDeviation.Min();
// Calculate standard deviation
decimal sumSquares = logReturns.Sum(x => (x - mean) * (x - mean));
decimal stdDev = (decimal)Math.Sqrt((double)(sumSquares / logReturns.Count));
if (stdDev == 0)
return;
// Calculate R/S statistic
decimal rs = range / stdDev;
// Hurst = log(R/S) / log(N)
decimal logN = (decimal)Math.Log((double)logReturns.Count);
if (logN != 0)
_hurstExponent = (decimal)Math.Log((double)rs) / logN;
}
}
import clr
import math
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
from StockSharp.Algo.Strategies import Strategy
class ichimoku_hurst_exponent_strategy(Strategy):
"""
Strategy based on Ichimoku Kinko Hyo indicator with Hurst exponent filter.
"""
def __init__(self):
super(ichimoku_hurst_exponent_strategy, self).__init__()
self._tenkan_period = self.Param("TenkanPeriod", 9) \
.SetDisplay("Tenkan Period", "Tenkan-sen (conversion line) period", "Ichimoku")
self._kijun_period = self.Param("KijunPeriod", 26) \
.SetDisplay("Kijun Period", "Kijun-sen (base line) period", "Ichimoku")
self._senkou_spanb_period = self.Param("SenkouSpanBPeriod", 52) \
.SetDisplay("Senkou Span B Period", "Senkou Span B (leading span B) period", "Ichimoku")
self._hurst_period = self.Param("HurstPeriod", 100) \
.SetDisplay("Hurst Period", "Hurst exponent calculation period", "Hurst Exponent")
self._hurst_threshold = self.Param("HurstThreshold", 0.5) \
.SetDisplay("Hurst Threshold", "Hurst exponent threshold for trend strength", "Hurst Exponent")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 6) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Bars to wait between reversals", "Trading")
self._prices = []
self._hurst_exponent = 0.5
self._prev_tenkan = None
self._prev_kijun = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ichimoku_hurst_exponent_strategy, self).OnReseted()
self._prices = []
self._hurst_exponent = 0.5
self._prev_tenkan = None
self._prev_kijun = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(ichimoku_hurst_exponent_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_spanb_period.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ichimoku, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ichimoku)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ichimoku_value):
if candle.State != CandleStates.Finished:
return
tenkan_val = ichimoku_value.Tenkan
kijun_val = ichimoku_value.Kijun
senkou_a_val = ichimoku_value.SenkouA
senkou_b_val = ichimoku_value.SenkouB
if tenkan_val is None or kijun_val is None or senkou_a_val is None or senkou_b_val is None:
return
tenkan = float(tenkan_val)
kijun = float(kijun_val)
senkou_a = float(senkou_a_val)
senkou_b = float(senkou_b_val)
hurst_period = int(self._hurst_period.Value)
self._prices.append(float(candle.ClosePrice))
while len(self._prices) > hurst_period:
self._prices.pop(0)
if len(self._prices) >= hurst_period:
self._calculate_hurst_exponent()
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
close_price = float(candle.ClosePrice)
is_price_above_kumo = close_price > max(senkou_a, senkou_b)
is_price_below_kumo = close_price < min(senkou_a, senkou_b)
cd = int(self._signal_cooldown_bars.Value)
ht = float(self._hurst_threshold.Value)
if self._prev_tenkan is not None and self._prev_kijun is not None:
cross_up = self._prev_tenkan <= self._prev_kijun and tenkan > kijun
cross_down = self._prev_tenkan >= self._prev_kijun and tenkan < kijun
long_exit = self.Position > 0 and (is_price_below_kumo or cross_down)
short_exit = self.Position < 0 and (is_price_above_kumo or cross_up)
if long_exit:
self.SellMarket(self.Position)
self._cooldown_remaining = cd
elif short_exit:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and is_price_above_kumo and cross_up and self._hurst_exponent > ht and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
self._cooldown_remaining = cd
elif self._cooldown_remaining == 0 and is_price_below_kumo and cross_down and self._hurst_exponent > ht and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
self._cooldown_remaining = cd
self._prev_tenkan = tenkan
self._prev_kijun = kijun
def _calculate_hurst_exponent(self):
log_returns = []
for i in range(1, len(self._prices)):
if self._prices[i - 1] != 0:
log_returns.append(math.log(self._prices[i] / self._prices[i - 1]))
if len(log_returns) < 10:
return
mean = sum(log_returns) / len(log_returns)
cumulative_deviation = []
sum_val = 0.0
for lr in log_returns:
sum_val += (lr - mean)
cumulative_deviation.append(sum_val)
range_val = max(cumulative_deviation) - min(cumulative_deviation)
sum_squares = sum((x - mean) ** 2 for x in log_returns)
std_dev = math.sqrt(sum_squares / len(log_returns))
if std_dev == 0:
return
rs = range_val / std_dev
log_n = math.log(len(log_returns))
if log_n != 0:
self._hurst_exponent = math.log(rs) / log_n
def CreateClone(self):
return ichimoku_hurst_exponent_strategy()