Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Hurst Exponent (HE) indicator #8434

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Algorithm/QCAlgorithm.Indicators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,26 @@ public HeikinAshi HeikinAshi(Symbol symbol, Resolution? resolution = null, Func<
return heikinAshi;
}

/// <summary>
/// Creates a new Hurst Exponent indicator for the specified symbol.
/// The Hurst Exponent measures the long-term memory or self-similarity in a time series.
/// The default maxLag value of 20 is chosen for reliable and accurate results, but using a higher lag may reduce precision.
/// </summary>
/// <param name="symbol">The symbol for which the Hurst Exponent is calculated.</param>
/// <param name="period">The number of data points used to calculate the indicator at each step.</param>
/// <param name="maxLag">The maximum time lag used to compute the tau values for the Hurst Exponent calculation.</param>
/// <param name="resolution">The resolution</param>
/// <param name="selector">Function to select a value from the BaseData to input into the indicator. Defaults to using the 'Value' property of BaseData if null.</param>
/// <returns>The Hurst Exponent indicator for the specified symbol.</returns>
[DocumentationAttribute(Indicators)]
public HurstExponent HE(Symbol symbol, int period, int maxLag = 20, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
{
var name = CreateIndicatorName(symbol, $"HE({period},{maxLag})", resolution);
var hurstExponent = new HurstExponent(name, period, maxLag);
InitializeIndicator(hurstExponent, resolution, selector, symbol);
return hurstExponent;
}

/// <summary>
/// Creates a new Hilbert Transform indicator
/// </summary>
Expand Down
164 changes: 164 additions & 0 deletions Indicators/HurstExponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Collections.Generic;

namespace QuantConnect.Indicators
{
/// <summary>
/// Represents the Hurst Exponent indicator, which is used to measure the long-term memory of a time series.
/// - H less than 0.5: Mean-reverting; high values followed by low ones, stronger as H approaches 0.
/// - H equal to 0.5: Random walk (geometric).
/// - H greater than 0.5: Trending; high values followed by higher ones, stronger as H approaches 1.
/// </summary>
public class HurstExponent : Indicator, IIndicatorWarmUpPeriodProvider
{
/// <summary>
/// A rolling window that holds the most recent price values.
/// </summary>
private readonly RollingWindow<decimal> _priceWindow;

/// <summary>
/// The list of time lags used to calculate tau values.
/// </summary>
private readonly List<int> _timeLags;

/// <summary>
/// Sum of the logarithms of the time lags, precomputed for efficiency.
/// </summary>
private readonly decimal _sumX;

/// <summary>
/// Sum of the squares of the logarithms of the time lags, precomputed for efficiency.
/// </summary>
private readonly decimal _sumX2;

/// <summary>
/// Initializes a new instance of the <see cref="HurstExponent"/> class.
/// The default maxLag value of 20 is chosen for reliable and accurate results, but using a higher lag may reduce precision.
/// </summary>
/// <param name="name">The name of the indicator.</param>
/// <param name="period">The period over which to calculate the Hurst Exponent.</param>
/// <param name="maxLag">The maximum lag to consider for time series analysis.</param>
public HurstExponent(string name, int period, int maxLag = 20) : base(name)
{
if (maxLag < 2)
{
throw new ArgumentException("The maxLag parameter must be greater than 2 to compute the Hurst Exponent.", nameof(maxLag));
}
_priceWindow = new RollingWindow<decimal>(period);
_timeLags = new List<int>();

// Precompute logarithms of time lags and their squares for regression calculations
for (var i = 2; i < maxLag; i++)
{
var logTimeLag = (decimal)Math.Log(i);
_timeLags.Add(i);
_sumX += logTimeLag;
_sumX2 += logTimeLag * logTimeLag;
}
WarmUpPeriod = period;
}

/// <summary>
/// Initializes a new instance of the <see cref="HurstExponent"/> class with the specified period and maxLag.
/// The default maxLag value of 20 is chosen for reliable and accurate results, but using a higher lag may reduce precision.
/// </summary>
/// <param name="period">The period over which to calculate the Hurst Exponent.</param>
/// <param name="maxLag">The maximum lag to consider for time series analysis.</param>
public HurstExponent(int period, int maxLag = 20)
: this($"HE({period},{maxLag})", period, maxLag)
{
}

/// <summary>
/// Gets the period over which the indicator is calculated.
/// </summary>
public int WarmUpPeriod { get; }

/// <summary>
/// Indicates whether the indicator has enough data to produce a valid result.
/// </summary>
public override bool IsReady => _priceWindow.IsReady;

/// <summary>
/// Computes the next value of the Hurst Exponent indicator.
/// </summary>
/// <param name="input">The input data point to use for the next value computation.</param>
/// <returns>The computed Hurst Exponent value, or zero if insufficient data is available.</returns>
protected override decimal ComputeNextValue(IndicatorDataPoint input)
{
_priceWindow.Add(input.Value);
if (!_priceWindow.IsReady)
{
return decimal.Zero;
}

// Sum of log(standard deviation) values
var sumY = 0m;

// Sum of log(lag) * log(standard deviation)
var sumXY = 0m;

foreach (var lag in _timeLags)
{
var mean = 0m;
var sumOfSquares = 0m;
var count = Math.Max(0, _priceWindow.Size - lag);
// Calculate the differences between values separated by the given lag
for (var i = 0; i < count; i++)
{
var value = _priceWindow[i + lag] - _priceWindow[i];
sumOfSquares += value * value;
mean += value;
}

var standardDeviation = 0.0;
// Avoid division by zero
if (count > 0)
{
mean = mean / count;
var variance = (sumOfSquares / count) - (mean * mean);
standardDeviation = Math.Sqrt((double)variance);
}

// Compute log(standard deviation) and log(lag) for the regression.
var logTau = standardDeviation == 0.0 ? 0m : (decimal)Math.Log(standardDeviation);
var logLag = (decimal)Math.Log(lag);

// Accumulate sums for the regression equation.
sumY += logTau;
sumXY += logLag * logTau;
}

// Number of time lags used for the computation
var n = _timeLags.Count;

// Compute the Hurst Exponent using the slope of the log-log regression.
var hurstExponent = (n * sumXY - _sumX * sumY) / (n * _sumX2 - _sumX * _sumX);
return hurstExponent;
}

/// <summary>
/// Resets the indicator to its initial state. This clears all internal data and resets
/// </summary>
public override void Reset()
{
_priceWindow.Reset();
base.Reset();
}
}
}
45 changes: 45 additions & 0 deletions Tests/Indicators/HurstExponentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using NUnit.Framework;
using QuantConnect.Indicators;

namespace QuantConnect.Tests.Indicators
{
[TestFixture]
public class HurstExponentTests : CommonIndicatorTests<IndicatorDataPoint>
{
protected override IndicatorBase<IndicatorDataPoint> CreateIndicator()
{
return new HurstExponent("HE", 252, 20);
}
protected override string TestFileName => "spy_hurst_exponent.csv";

protected override string TestColumnName => "hurst_exponent";

[Test]
public void DoesNotThrowDivisionByZero()
{
var he = new HurstExponent(2);
var date = new DateTime(2024, 12, 2, 12, 0, 0);

for (var i = 0; i < 10; i++)
{
Assert.DoesNotThrow(() => he.Update(date, 0m));
}
}
}
}
3 changes: 3 additions & 0 deletions Tests/QuantConnect.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@
<Content Include="TestData\spy_crsi.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="TestData\spy_hurst_exponent.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Content Include="TestData\symbol-properties\symbol-properties-database.csv">
Expand Down
Loading