Skip to content

Commit

Permalink
Implement Hurst Exponent indicator and unit test
Browse files Browse the repository at this point in the history
- Implemented Hurst Exponent indicator class
- Created unit tests to validate indicator functionality
- Added helper methods for standard deviation and regression line slope
  • Loading branch information
JosueNina committed Nov 29, 2024
1 parent 01734e1 commit 9661146
Show file tree
Hide file tree
Showing 5 changed files with 1,226 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="lookbackPeriod">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 lookbackPeriod, int maxLag = 20, Resolution? resolution = null, Func<IBaseData, decimal> selector = null)
{
var name = CreateIndicatorName(symbol, $"HE({lookbackPeriod},{maxLag})", resolution);
var hurstExponent = new HurstExponent(name, lookbackPeriod, maxLag);
InitializeIndicator(hurstExponent, resolution, selector, symbol);
return hurstExponent;
}

/// <summary>
/// Creates a new Hilbert Transform indicator
/// </summary>
Expand Down
158 changes: 158 additions & 0 deletions Indicators/HurstExponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* 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;
using System.Linq;

namespace QuantConnect.Indicators
{
/// <summary>
/// Represents the Hurst Exponent indicator, which is used to measure the long-term memory of a time series.
/// </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 List<int> timeLags;

/// <summary>
/// The list of the logarithms of the time lags, used for the regression line calculation.
/// </summary>
private List<decimal> logTimeLags;

/// <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="lookbackPeriod">The lookbackPeriod 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 lookbackPeriod, int maxLag = 20) : base(name)
{
_priceWindow = new RollingWindow<decimal>(lookbackPeriod);
timeLags = Enumerable.Range(2, maxLag - 2).ToList();
logTimeLags = timeLags.Select(x => (decimal)Math.Log(x)).ToList();
WarmUpPeriod = lookbackPeriod;
}

/// <summary>
/// Initializes a new instance of the <see cref="HurstExponent"/> class with the specified lookbackPeriod 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="lookbackPeriod">The lookbackPeriod over which to calculate the Hurst Exponent.</param>
/// <param name="maxLag">The maximum lag to consider for time series analysis.</param>
public HurstExponent(int lookbackPeriod, int maxLag = 20)
: this($"HE({lookbackPeriod},{maxLag})", lookbackPeriod, maxLag)
{
}

/// <summary>
/// Gets the lookbackPeriod 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;
}

// List to store the log of tau values for each time lag
var logTauValues = new List<decimal>();
foreach (var lag in timeLags)
{
var sub = new List<decimal>();
// Calculate the differences between values separated by the given lag
for (int i = _priceWindow.Size - 1 - lag; i >= 0; i--)
{
var value = _priceWindow[i] - _priceWindow[i + lag];
sub.Add(value);
}
var standardDeviation = 0.0;
// Ensure sub is not empty to avoid division by zero.
if (sub.Count > 0)
{
standardDeviation = ComputeStandardDeviation(sub);
}
logTauValues.Add(standardDeviation == 0.0 ? 0m : (decimal)Math.Log(standardDeviation));
}

// Calculate the Hurst Exponent as the slope of the log-log plot
var hurstExponent = ComputeSlope(logTimeLags, logTauValues);
if (IsReady)
{
return hurstExponent;
}
return decimal.Zero;
}

/// <summary>
/// Calculates the standard deviation of a list of decimal values.
/// </summary>
/// <param name="values">The list of values to calculate the standard deviation for.</param>
/// <returns>The standard deviation of the given values.</returns>
private double ComputeStandardDeviation(IEnumerable<decimal> values)
{
var avg = values.Average();
var variance = values.Sum(x => (x - avg) * (x - avg)) / values.Count();
return Math.Sqrt((double)variance);
}

/// <summary>
/// Calculates the slope of the regression line through a set of data points.
/// </summary>
/// <param name="x">The x-coordinates of the data points.</param>
/// <param name="y">The y-coordinates of the data points.</param>
/// <returns>The slope of the regression line.</returns>
public decimal ComputeSlope(IEnumerable<decimal> x, IEnumerable<decimal> y)
{
int n = x.Count();
var sumX = x.Sum();
var sumY = y.Sum();
var sumXY = x.Zip(y, (xi, yi) => xi * yi).Sum();
var sumX2 = x.Select(xi => xi * xi).Sum();
var m = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
return m;
}

/// <summary>
/// Resets the indicator to its initial state. This clears all internal data and resets
/// </summary>
public override void Reset()
{
_priceWindow.Reset();
base.Reset();
}
}
}
43 changes: 43 additions & 0 deletions Tests/Indicators/HurstExponentTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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);
for (var i = 0; i < 10; i++)
{
Assert.DoesNotThrow(() => he.Update(DateTime.UtcNow, 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

0 comments on commit 9661146

Please sign in to comment.