/*
* 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
{
///
/// 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.
///
public class HurstExponent : Indicator, IIndicatorWarmUpPeriodProvider
{
///
/// A rolling window that holds the most recent price values.
///
private readonly RollingWindow _priceWindow;
///
/// The list of time lags used to calculate tau values.
///
private readonly List _timeLags;
///
/// Sum of the logarithms of the time lags, precomputed for efficiency.
///
private readonly decimal _sumX;
///
/// Sum of the squares of the logarithms of the time lags, precomputed for efficiency.
///
private readonly decimal _sumX2;
///
/// Initializes a new instance of the class.
/// The default maxLag value of 20 is chosen for reliable and accurate results, but using a higher lag may reduce precision.
///
/// The name of the indicator.
/// The period over which to calculate the Hurst Exponent.
/// The maximum lag to consider for time series analysis.
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(period);
_timeLags = new List();
// 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;
}
///
/// Initializes a new instance of the 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.
///
/// The period over which to calculate the Hurst Exponent.
/// The maximum lag to consider for time series analysis.
public HurstExponent(int period, int maxLag = 20)
: this($"HE({period},{maxLag})", period, maxLag)
{
}
///
/// Gets the period over which the indicator is calculated.
///
public int WarmUpPeriod { get; }
///
/// Indicates whether the indicator has enough data to produce a valid result.
///
public override bool IsReady => _priceWindow.IsReady;
///
/// Computes the next value of the Hurst Exponent indicator.
///
/// The input data point to use for the next value computation.
/// The computed Hurst Exponent value, or zero if insufficient data is available.
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;
}
///
/// Resets the indicator to its initial state. This clears all internal data and resets
///
public override void Reset()
{
_priceWindow.Reset();
base.Reset();
}
}
}