Skip to content

Commit

Permalink
Implement Hurst Exponent (HE) indicator (#8434)
Browse files Browse the repository at this point in the history
* Implement Hurst Exponent indicator and unit test

- Implemented Hurst Exponent indicator class
- Created unit tests to validate indicator functionality
- Added helper methods for standard deviation and regression line slope

* Optimize Hurst Exponent calculation

- Removed redundat loops and unnecessary operations.
- Precomputed values fro time lag logarithms to avoid recalculation.
- Simplified logic in ComputeNextValue for better performance.
- Updated and clarified comments for improved readability.

* Refactor HurstExponent indicator

- Made _sumX and _sumX2 fields readonly.
- Updated loop for calculating time lag differences to avoid out of
  bound.
- Renamed lookbackPeriod with period.
- Replaced DateTime.Now with a fixed date.
  • Loading branch information
JosueNina authored Dec 5, 2024
1 parent 9955892 commit 8e5893b
Show file tree
Hide file tree
Showing 5 changed files with 1,234 additions and 0 deletions.
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 @@ -615,6 +615,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

0 comments on commit 8e5893b

Please sign in to comment.