在 GitHub 上查看

Ichimoku Hurst Exponent

Ichimoku Hurst Exponent 策略基于 Ichimoku Kinko Hyo indicator with Hurst exponent filter。

测试表明年均收益约为 64%,该策略在外汇市场表现最佳。

当 Hurst confirms trend changes 在日内(15m)数据上得到确认时触发信号,适合积极交易者。

止损依赖于 ATR 倍数以及 TenkanPeriod, KijunPeriod 等参数,可根据需要调整以平衡风险与收益。

详情

  • 入场条件:参见指标条件实现.
  • 多空方向:双向.
  • 退出条件:反向信号或止损逻辑.
  • 止损:是,基于指标计算.
  • 默认值:
    • TenkanPeriod = 9
    • KijunPeriod = 26
    • SenkouSpanBPeriod = 52
    • HurstPeriod = 100
    • HurstThreshold = 0.5m
    • CandleType = 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;

	}
}