diff --git a/Directory.Build.props b/Directory.Build.props index a5cfd53c..a6c4ab71 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,7 +8,6 @@ true en-US false - false true AnyCPU True @@ -21,6 +20,9 @@ snupkg AnyCPU true + true + true + true @@ -49,6 +51,7 @@ + diff --git a/Tests/test_updates_statistics.cs b/Tests/test_updates_statistics.cs index 6fca5957..d60fb181 100644 --- a/Tests/test_updates_statistics.cs +++ b/Tests/test_updates_statistics.cs @@ -17,6 +17,15 @@ public class StatisticsUpdateTests return (double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200 - 100; // Range: -100 to 100 } + private TBar GetRandomBar(bool IsNew) + { + double open = GetRandomDouble(); + double high = open + Math.Abs(GetRandomDouble()); + double low = open - Math.Abs(GetRandomDouble()); + double close = low + (high - low) * GetRandomDouble(); + return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew); + } + [Fact] public void Curvature_Update() { @@ -47,6 +56,22 @@ public class StatisticsUpdateTests Assert.Equal(initialValue, finalValue, precision); } + [Fact] + public void Hurst_Update() + { + var indicator = new Hurst(period: 100, minLength: 10); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Kurtosis_Update() { diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs index 8bb6e5c4..696c105f 100644 --- a/Tests/test_updates_volatility.cs +++ b/Tests/test_updates_volatility.cs @@ -90,6 +90,134 @@ public class VolatilityUpdateTests Assert.Equal(initialValue, finalValue, precision); } + [Fact] + public void Bband_Update() + { + var indicator = new Bband(period: 20, multiplier: 2.0); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Ccv_Update() + { + var indicator = new Ccv(period: 20); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Ce_Update() + { + var indicator = new Ce(period: 22, multiplier: 3.0); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Cv_Update() + { + var indicator = new Cv(period: 20); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Cvi_Update() + { + var indicator = new Cvi(period: 10, smoothPeriod: 10); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Ewma_Update() + { + var indicator = new Ewma(period: 20, lambda: 0.94); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Fcb_Update() + { + var indicator = new Fcb(period: 20, smoothing: 0.5); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Gkv_Update() + { + var indicator = new Gkv(period: 20); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Historical_Update() { @@ -105,6 +233,22 @@ public class VolatilityUpdateTests Assert.Equal(initialValue, finalValue, precision); } + [Fact] + public void Hlv_Update() + { + var indicator = new Hlv(period: 20); + TBar r = GetRandomBar(true); + double initialValue = indicator.Calc(r); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(GetRandomBar(IsNew: false)); + } + double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + [Fact] public void Jvolty_Update() { @@ -150,22 +294,6 @@ public class VolatilityUpdateTests Assert.Equal(initialValue, finalValue, precision); } - [Fact] - public void Cvi_Update() - { - var indicator = new Cvi(period: 14); - TBar r = GetRandomBar(true); - double initialValue = indicator.Calc(r); - - for (int i = 0; i < RandomUpdates; i++) - { - indicator.Calc(GetRandomBar(IsNew: false)); - } - double finalValue = indicator.Calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false)); - - Assert.Equal(initialValue, finalValue, precision); - } - [Fact] public void Tr_Update() { diff --git a/docs/indicators/indicators.md b/docs/indicators/indicators.md index 4d97d32c..0aecedff 100644 --- a/docs/indicators/indicators.md +++ b/docs/indicators/indicators.md @@ -1,16 +1,18 @@ # Indicators in QuanTAlib + + | **Category** | **Status** | **Completion** | |--------------|:----------:|:--------------:| | Basic Transforms | 6 of 6 | 100% | | Averages & Trends | 33 of 33 | 100% | | Momentum | 17 of 17 | 100% | -| Oscillators | 12 of 29 | 41% | -| Volatility | 15 of 35 | 43% | -| Volume | 19 of 19 | 100% | -| Numerical Analysis | 13 of 20 | 65% | +| Oscillators | 11 of 29 | 38% | +| Volatility | 24 of 35 | 69% | +| Volume | 15 of 19 | 79% | +| Numerical Analysis | 13 of 19 | 68% | | Errors | 16 of 16 | 100% | -| **Total** | **131 of 175** | **75%** | +| **Total** | **135 of 174** | **78%** | |Technical Indicator Name| Class Name| |-----------|:----------:| @@ -109,16 +111,16 @@ |ATR - Average True Range|`Atr`| |ATRP - Average True Range Percent|`Atrp`| |ATRS - ATR Trailing Stop|`Atrs`| -|🚧 BB* - Bollinger Bands® (Upper, Middle, Lower)|`Bb`| -|🚧 CCV - Close-to-Close Volatility|`Ccv`| -|🚧 CE - Chandelier Exit|`Ce`| -|🚧 CV - Conditional Volatility (ARCH/GARCH)|`Cv`| -|🚧 CVI - Chaikin's Volatility|`Cvi`| +|BB* - Bollinger Bands® (Upper, Middle, Lower)|`Bb`| +|CCV - Close-to-Close Volatility|`Ccv`| +|CE - Chandelier Exit|`Ce`| +|CV - Conditional Volatility (ARCH/GARCH)|`Cv`| +|CVI - Chaikin's Volatility|`Cvi`| |🚧 DC* - Donchian Channels (Upper, Middle, Lower)|`Dc`| -|🚧 EWMA - Exponential Weighted Moving Average Volatility|`Ewma`| -|🚧 FCB - Fractal Chaos Bands|`Fcb`| -|🚧 GKV - Garman-Klass Volatility|`Gkv`| -|🚧 HLV - High-Low Volatility|`Hlv`| +|EWMA - Exponential Weighted Moving Average Volatility|`Ewma`| +|FCB - Fractal Chaos Bands|`Fcb`| +|GKV - Garman-Klass Volatility|`Gkv`| +|HLV - High-Low Volatility|`Hlv`| |HV - Historical Volatility|`Hv`| |🚧 ICH* - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span)|`Ich`| |JVOLTY - Jurik Volatility|`Jvolty`| diff --git a/lib/quantalib.csproj b/lib/quantalib.csproj index a54f910e..a5bae0db 100644 --- a/lib/quantalib.csproj +++ b/lib/quantalib.csproj @@ -1,36 +1,27 @@ - QuanTAlib Library of TA Calculations, Charts and Strategies for Quantower Quantitative Technical Analysis Library in C# for Quantower git https://github.com/mihakralj/QuanTAlib - true Miha Kralj Miha Kralj readme.md QuanTAlib QuanTAlib - 0.0.0.1 True - AnyCPU - full - True True QuanTAlib2.png - readme.md Apache-2.0 Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo; AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex; Quantitative;Historical;Quotes; - QuanTAlib2.png https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png True false - $(NoWarn);NU1903;NU5104 @@ -51,4 +42,4 @@ - + \ No newline at end of file diff --git a/lib/statistics/Hurst.cs b/lib/statistics/Hurst.cs new file mode 100644 index 00000000..4ec62254 --- /dev/null +++ b/lib/statistics/Hurst.cs @@ -0,0 +1,194 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// HURST: Hurst Exponent +/// A measure of long-term memory of time series that relates to the +/// autocorrelations of the time series, and the rate at which these +/// decrease as the lag between pairs of values increases. +/// +/// +/// The Hurst Exponent calculation process: +/// 1. Calculate log returns of the series +/// 2. Create subsequences of different lengths +/// 3. For each length: +/// - Calculate range (max-min) of cumulative deviations +/// - Calculate standard deviation +/// - Calculate R/S ratio +/// 4. Fit log(R/S) vs log(length) to find H +/// +/// Key characteristics: +/// - H = 0.5: Random walk (Brownian motion) +/// - 0.5 < H ≤ 1.0: Trending (persistent) series +/// - 0 ≤ H < 0.5: Mean-reverting (anti-persistent) series +/// - Default minimum length is 10 +/// - Default maximum length is period/2 +/// +/// Formula: +/// R(n)/S(n) = c * n^H +/// where: +/// R(n) = range of cumulative deviations +/// S(n) = standard deviation +/// n = subsequence length +/// H = Hurst exponent +/// +/// Market Applications: +/// - Market efficiency analysis +/// - Trend strength measurement +/// - Trading strategy development +/// - Risk assessment +/// - Market regime identification +/// +/// Sources: +/// H.E. Hurst (1951) +/// "Long-term Storage Capacity of Reservoirs" +/// Transactions of the American Society of Civil Engineers, 116, 770-799 +/// +/// Note: Returns a value between 0 and 1 +/// + +[SkipLocalsInit] +public sealed class Hurst : AbstractBase +{ + private readonly int _period; + private readonly int _minLength; + private readonly CircularBuffer _prices; + private readonly CircularBuffer _logReturns; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Hurst(int period = 100, int minLength = 10) + { + if (minLength < 10) + { + throw new ArgumentOutOfRangeException(nameof(minLength), "Minimum length must be at least 10."); + } + if (period <= minLength * 2) + { + throw new ArgumentOutOfRangeException(nameof(period), "Period must be at least twice the minimum length."); + } + + _period = period; + _minLength = minLength; + WarmupPeriod = period + 1; // Need one extra period for returns + Name = $"HURST({_period})"; + _prices = new CircularBuffer(period); + _logReturns = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Hurst(object source, int period = 100, int minLength = 10) : this(period, minLength) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prices.Clear(); + _logReturns.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static (double range, double stdDev) CalculateRangeAndStdDev(ReadOnlySpan data) + { + int n = data.Length; + if (n == 0) return (0, 0); + + // Calculate mean + double mean = 0; + for (int i = 0; i < n; i++) + { + mean += data[i]; + } + mean /= n; + + // Calculate cumulative deviations and std dev + double max = double.MinValue; + double min = double.MaxValue; + double sumSquaredDev = 0; + double cumDev = 0; + + for (int i = 0; i < n; i++) + { + double dev = data[i] - mean; + cumDev += dev; + max = Math.Max(max, cumDev); + min = Math.Min(min, cumDev); + sumSquaredDev += dev * dev; + } + + double range = max - min; + double stdDev = Math.Sqrt(sumSquaredDev / n); + + return (range, stdDev); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Add price and calculate log return + _prices.Add(BarInput.Close); + if (_index > 1) + { + double logReturn = Math.Log(BarInput.Close / _prices[1]); + _logReturns.Add(logReturn); + } + + // Need enough values for calculation + if (_index <= _period) + { + return 0.5; // Return random walk value until we have enough data + } + + // Calculate R/S values for different lengths + int maxLength = _period / 2; + int numPoints = 0; + double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0; + + for (int length = _minLength; length <= maxLength; length *= 2) + { + var (range, stdDev) = CalculateRangeAndStdDev(_logReturns.GetSpan()[..length]); + if (stdDev > 0) + { + double rs = range / stdDev; + if (rs > 0) + { + double x = Math.Log(length); + double y = Math.Log(rs); + sumX += x; + sumY += y; + sumXY += x * y; + sumX2 += x * x; + numPoints++; + } + } + } + + // Calculate Hurst exponent using linear regression + double hurst = 0.5; // Default to random walk + if (numPoints > 1) + { + double slope = (numPoints * sumXY - sumX * sumY) / (numPoints * sumX2 - sumX * sumX); + hurst = Math.Max(0, Math.Min(1, slope)); // Clamp between 0 and 1 + } + + IsHot = _index >= WarmupPeriod; + return hurst; + } +} diff --git a/lib/volatility/Bband.cs b/lib/volatility/Bband.cs new file mode 100644 index 00000000..a135b71b --- /dev/null +++ b/lib/volatility/Bband.cs @@ -0,0 +1,143 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// BBAND: Bollinger Bands® +/// A technical analysis tool that creates a band of three lines: +/// - Middle Band: n-period simple moving average (SMA) +/// - Upper Band: Middle Band + (standard deviation * multiplier) +/// - Lower Band: Middle Band - (standard deviation * multiplier) +/// +/// +/// The Bollinger Bands calculation process: +/// 1. Calculate the middle band (SMA of closing prices) +/// 2. Calculate the standard deviation of prices +/// 3. Upper and lower bands are the middle band +/- standard deviation * multiplier +/// +/// Key characteristics: +/// - Adapts to volatility +/// - Default period is 20 days +/// - Default multiplier is 2.0 +/// - Returns three bands (upper, middle, lower) +/// - Wider bands indicate higher volatility +/// - Narrower bands indicate lower volatility +/// +/// Formula: +/// Middle Band = SMA(Close, period) +/// Standard Deviation = SQRT(SUM((Close - Middle Band)^2) / period) +/// Upper Band = Middle Band + (multiplier * Standard Deviation) +/// Lower Band = Middle Band - (multiplier * Standard Deviation) +/// +/// Market Applications: +/// - Volatility measurement +/// - Overbought/oversold identification +/// - Price breakout detection +/// - Trend strength analysis +/// - Dynamic support/resistance levels +/// +/// Sources: +/// John Bollinger (1980s) +/// https://www.bollingerbands.com +/// +/// Note: Returns three values: upper, middle, and lower bands +/// + +[SkipLocalsInit] +public sealed class Bband : AbstractBase +{ + private readonly int _period; + private readonly double _multiplier; + private readonly CircularBuffer _prices; + private double _middleBand; + private double _upperBand; + private double _lowerBand; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Bband(int period = 20, double multiplier = 2.0) + { + _period = period; + _multiplier = multiplier; + WarmupPeriod = period; + Name = $"BBAND({_period},{_multiplier})"; + _prices = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Bband(object source, int period = 20, double multiplier = 2.0) : this(period, multiplier) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _middleBand = 0; + _upperBand = 0; + _lowerBand = 0; + _prices.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Add current price to buffer + _prices.Add(BarInput.Close); + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate middle band (SMA) + _middleBand = _prices.Average(); + + // Calculate standard deviation + double sumSquaredDeviations = 0; + for (int i = 0; i < _period; i++) + { + double deviation = _prices[i] - _middleBand; + sumSquaredDeviations += deviation * deviation; + } + double standardDeviation = Math.Sqrt(sumSquaredDeviations / _period); + + // Calculate bands + double bandWidth = _multiplier * standardDeviation; + _upperBand = _middleBand + bandWidth; + _lowerBand = _middleBand - bandWidth; + + IsHot = _index >= WarmupPeriod; + return _middleBand; // Return middle band as primary value + } + + /// + /// Gets the upper band value + /// + public double UpperBand => _upperBand; + + /// + /// Gets the middle band value (SMA) + /// + public double MiddleBand => _middleBand; + + /// + /// Gets the lower band value + /// + public double LowerBand => _lowerBand; +} diff --git a/lib/volatility/Ccv.cs b/lib/volatility/Ccv.cs new file mode 100644 index 00000000..cfe6e7ba --- /dev/null +++ b/lib/volatility/Ccv.cs @@ -0,0 +1,130 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// CCV: Close-to-Close Volatility +/// A measure of price volatility that uses only closing prices, +/// calculated as the standard deviation of logarithmic returns. +/// +/// +/// The CCV calculation process: +/// 1. Calculate logarithmic returns: ln(Close[t]/Close[t-1]) +/// 2. Calculate standard deviation of returns over the period +/// 3. Annualize by multiplying by sqrt(trading days per year) +/// +/// Key characteristics: +/// - Uses only closing prices +/// - Based on logarithmic returns +/// - Default period is 20 days +/// - Annualized by default (multiply by sqrt(252)) +/// - Expressed as a percentage +/// +/// Formula: +/// Returns = ln(Close[t]/Close[t-1]) +/// CCV = StdDev(Returns, period) * sqrt(252) * 100 +/// +/// Market Applications: +/// - Volatility measurement +/// - Risk assessment +/// - Option pricing +/// - Trading strategy development +/// - Portfolio management +/// +/// Sources: +/// Close-to-Close Volatility concept +/// https://www.investopedia.com/terms/v/volatility.asp +/// +/// Note: Returns annualized volatility as a percentage +/// + +[SkipLocalsInit] +public sealed class Ccv : AbstractBase +{ + private readonly int _period; + private readonly bool _annualize; + private readonly CircularBuffer _returns; + private double _prevClose; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ccv(int period = 20, bool annualize = true) + { + _period = period; + _annualize = annualize; + WarmupPeriod = period + 1; // Need one extra period for returns calculation + Name = $"CCV({_period})"; + _returns = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ccv(object source, int period = 20, bool annualize = true) : this(period, annualize) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _returns.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate logarithmic return + double logReturn = Math.Log(BarInput.Close / _prevClose); + _returns.Add(logReturn); + _prevClose = BarInput.Close; + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate standard deviation + double mean = _returns.Average(); + double sumSquaredDeviations = 0; + for (int i = 0; i < _period; i++) + { + double deviation = _returns[i] - mean; + sumSquaredDeviations += deviation * deviation; + } + double stdDev = Math.Sqrt(sumSquaredDeviations / _period); + + // Annualize if requested (sqrt(252) for trading days in a year) + if (_annualize) + { + stdDev *= Math.Sqrt(252); + } + + // Convert to percentage + double volatility = stdDev * 100; + + IsHot = _index >= WarmupPeriod; + return volatility; + } +} diff --git a/lib/volatility/Ce.cs b/lib/volatility/Ce.cs new file mode 100644 index 00000000..225c2c22 --- /dev/null +++ b/lib/volatility/Ce.cs @@ -0,0 +1,157 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// CE: Chandelier Exit +/// A volatility-based stop-loss indicator that adapts to market conditions, +/// using ATR to set stop levels above/below recent price extremes. +/// +/// +/// The CE calculation process: +/// 1. Calculate highest high and lowest low over the period +/// 2. Calculate ATR over the period +/// 3. Long Exit = Highest High - (ATR * multiplier) +/// 4. Short Exit = Lowest Low + (ATR * multiplier) +/// +/// Key characteristics: +/// - Adapts to market volatility +/// - Default period is 22 days +/// - Default multiplier is 3.0 +/// - Returns both long and short exit levels +/// - Based on ATR and price extremes +/// +/// Formula: +/// ATR = Average(TR, period) +/// Long Exit = Highest High[period] - (multiplier * ATR) +/// Short Exit = Lowest Low[period] + (multiplier * ATR) +/// +/// Market Applications: +/// - Stop loss placement +/// - Position management +/// - Trend following +/// - Risk control +/// - Exit strategy +/// +/// Sources: +/// Chuck LeBeau +/// https://www.investopedia.com/terms/c/chandelier-exit.asp +/// +/// Note: Returns two values: long exit and short exit levels +/// + +[SkipLocalsInit] +public sealed class Ce : AbstractBase +{ + private readonly int _period; + private readonly double _multiplier; + private readonly CircularBuffer _tr; + private readonly CircularBuffer _highs; + private readonly CircularBuffer _lows; + private double _prevClose; + private double _longExit; + private double _shortExit; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ce(int period = 22, double multiplier = 3.0) + { + _period = period; + _multiplier = multiplier; + WarmupPeriod = period + 1; // Need one extra period for TR + Name = $"CE({_period},{_multiplier})"; + _tr = new CircularBuffer(period); + _highs = new CircularBuffer(period); + _lows = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ce(object source, int period = 22, double multiplier = 3.0) : this(period, multiplier) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _longExit = 0; + _shortExit = 0; + _tr.Clear(); + _highs.Clear(); + _lows.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate True Range + double tr = Math.Max(BarInput.High - BarInput.Low, + Math.Max(Math.Abs(BarInput.High - _prevClose), + Math.Abs(BarInput.Low - _prevClose))); + + // Add values to buffers + _tr.Add(tr); + _highs.Add(BarInput.High); + _lows.Add(BarInput.Low); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate ATR + double atr = _tr.Average(); + + // Find highest high and lowest low + double highestHigh = double.MinValue; + double lowestLow = double.MaxValue; + for (int i = 0; i < _period; i++) + { + highestHigh = Math.Max(highestHigh, _highs[i]); + lowestLow = Math.Min(lowestLow, _lows[i]); + } + + // Calculate exit levels + _longExit = highestHigh - (_multiplier * atr); + _shortExit = lowestLow + (_multiplier * atr); + + IsHot = _index >= WarmupPeriod; + return _longExit; // Return long exit as primary value + } + + /// + /// Gets the long exit level + /// + public double LongExit => _longExit; + + /// + /// Gets the short exit level + /// + public double ShortExit => _shortExit; +} diff --git a/lib/volatility/Cv.cs b/lib/volatility/Cv.cs new file mode 100644 index 00000000..2d1f88c2 --- /dev/null +++ b/lib/volatility/Cv.cs @@ -0,0 +1,140 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// CV: Conditional Volatility (GARCH) +/// Implements the GARCH(1,1) model for estimating conditional volatility, +/// which captures volatility clustering and mean reversion in financial markets. +/// +/// +/// The CV (GARCH) calculation process: +/// 1. Calculate returns: (Close[t] - Close[t-1])/Close[t-1] +/// 2. Update variance estimate using GARCH(1,1) formula: +/// σ²[t] = ω + α*r²[t-1] + β*σ²[t-1] +/// 3. Take square root to get volatility +/// +/// Key characteristics: +/// - Captures volatility clustering +/// - Mean-reverting behavior +/// - Responds to market shocks +/// - Default period is 20 days +/// - Returns annualized volatility +/// +/// Formula: +/// Returns[t] = (Close[t] - Close[t-1])/Close[t-1] +/// σ²[t] = ω + α*Returns²[t-1] + β*σ²[t-1] +/// CV[t] = sqrt(σ²[t]) * sqrt(252) * 100 +/// +/// Where: +/// ω (omega) = long-term variance * (1 - α - β) +/// α (alpha) = weight of recent squared return +/// β (beta) = weight of previous variance +/// +/// Market Applications: +/// - Risk measurement +/// - Option pricing +/// - Value at Risk (VaR) +/// - Portfolio optimization +/// - Volatility forecasting +/// +/// Sources: +/// Bollerslev (1986) +/// https://en.wikipedia.org/wiki/GARCH +/// +/// Note: Returns annualized volatility as a percentage +/// + +[SkipLocalsInit] +public sealed class Cv : AbstractBase +{ + private readonly int _period; + private readonly double _alpha; + private readonly double _beta; + private readonly double _omega; + private double _prevClose; + private double _prevVariance; + private double _longTermVariance; + private bool _isInitialized; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cv(int period = 20, double alpha = 0.1, double beta = 0.8) + { + _period = period; + _alpha = alpha; + _beta = beta; + _omega = 0.001 * (1 - alpha - beta); // Initial estimate, will be updated with actual data + WarmupPeriod = period + 1; // Need one extra period for returns + Name = $"CV({_period})"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cv(object source, int period = 20, double alpha = 0.1, double beta = 0.8) : this(period, alpha, beta) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _prevVariance = 0; + _longTermVariance = 0; + _isInitialized = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate return + double return_ = (BarInput.Close - _prevClose) / _prevClose; + double squaredReturn = return_ * return_; + _prevClose = BarInput.Close; + + // Initialize with first available data if not done + if (!_isInitialized && _index > _period) + { + _longTermVariance = squaredReturn; // Use current squared return as initial estimate + _prevVariance = _longTermVariance; + _isInitialized = true; + } + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Update variance estimate using GARCH(1,1) + double variance = _omega + _alpha * squaredReturn + _beta * _prevVariance; + _prevVariance = variance; + + // Calculate annualized volatility as percentage + double volatility = Math.Sqrt(variance) * Math.Sqrt(252) * 100; + + IsHot = _index >= WarmupPeriod; + return volatility; + } +} diff --git a/lib/volatility/Cvi.cs b/lib/volatility/Cvi.cs index a6895321..99d1687b 100644 --- a/lib/volatility/Cvi.cs +++ b/lib/volatility/Cvi.cs @@ -2,66 +2,66 @@ using System.Runtime.CompilerServices; namespace QuanTAlib; /// -/// CVI: Chaikin's Volatility -/// A technical indicator developed by Marc Chaikin that measures the volatility of a financial instrument by comparing the spread between the high and low prices. +/// CVI: Chaikin's Volatility Index +/// Measures the rate of change of a moving average of the difference +/// between high and low prices, indicating volatility expansion/contraction. /// /// /// The CVI calculation process: -/// 1. Calculates the difference between the high and low prices. -/// 2. Applies an exponential moving average (EMA) to the differences. -/// 3. Computes the percentage change in the EMA over a specified period. +/// 1. Calculate High-Low difference +/// 2. Take EMA of High-Low difference +/// 3. Calculate ROC of the EMA over specified period /// /// Key characteristics: -/// - Measures volatility -/// - Uses high and low prices -/// - Percentage-based -/// - EMA smoothing +/// - Measures volatility expansion/contraction +/// - Default period is 10 days +/// - Default smoothing period is 10 days +/// - Positive values indicate expanding volatility +/// - Negative values indicate contracting volatility /// /// Formula: -/// CVI = (EMA(high - low, period) - EMA(high - low, period, offset)) / EMA(high - low, period, offset) * 100 +/// HL = High - Low +/// Smoothed = EMA(HL, smoothPeriod) +/// CVI = ((Smoothed - Smoothed[period]) / Smoothed[period]) * 100 /// /// Market Applications: -/// - Volatility assessment -/// - Trend confirmation -/// - Risk management -/// - Entry/exit timing +/// - Volatility measurement +/// - Trend strength analysis +/// - Market regime identification +/// - Trading range analysis +/// - Breakout confirmation /// /// Sources: -/// Marc Chaikin - Original development -/// https://www.investopedia.com/terms/c/chaikins-volatility.asp +/// Marc Chaikin +/// https://www.investopedia.com/terms/c/chaikinvolatility.asp /// -/// Note: Higher CVI values indicate higher volatility +/// Note: Returns percentage change in volatility /// [SkipLocalsInit] public sealed class Cvi : AbstractBase { private readonly int _period; - private readonly Ema _ema; - private readonly CircularBuffer _buffer; - private double _prevEma; + private readonly int _smoothPeriod; + private readonly CircularBuffer _smoothed; + private readonly double _alpha; + private double _ema; - /// The number of periods for CVI calculation. - /// Thrown when period is less than 1. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cvi(int period) + public Cvi(int period = 10, int smoothPeriod = 10) { - if (period < 1) - { - throw new ArgumentOutOfRangeException(nameof(period), - "Period must be greater than or equal to 1."); - } _period = period; - _ema = new Ema(period); - _buffer = new CircularBuffer(period); - WarmupPeriod = period; - Name = $"CVI({period})"; + _smoothPeriod = smoothPeriod; + _alpha = 2.0 / (_smoothPeriod + 1); + WarmupPeriod = _period + _smoothPeriod; + Name = $"CVI({_period},{_smoothPeriod})"; + _smoothed = new CircularBuffer(_period); + Init(); } /// The data source object that publishes updates. - /// The number of periods for CVI calculation. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Cvi(object source, int period) : this(period) + public Cvi(object source, int period = 10, int smoothPeriod = 10) : this(period, smoothPeriod) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new BarSignal(Sub)); @@ -71,9 +71,8 @@ public sealed class Cvi : AbstractBase public override void Init() { base.Init(); - _ema.Init(); - _buffer.Clear(); - _prevEma = 0; + _ema = 0; + _smoothed.Clear(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -81,6 +80,7 @@ public sealed class Cvi : AbstractBase { if (isNew) { + _lastValidValue = Value; _index++; } } @@ -90,20 +90,32 @@ public sealed class Cvi : AbstractBase { ManageState(BarInput.IsNew); - double highLowDiff = BarInput.High - BarInput.Low; - _buffer.Add(highLowDiff, BarInput.IsNew); + // Calculate High-Low difference + double hl = BarInput.High - BarInput.Low; - double ema = _ema.Calc(new TValue(Input.Time, highLowDiff, BarInput.IsNew)).Value; - - double cvi = 0; - if (_index >= _period) + // Calculate EMA of High-Low difference + if (_index == 1) { - double prevEma = _buffer[_buffer.Count - _period]; - cvi = (ema - prevEma) / prevEma * 100; + _ema = hl; + } + else + { + _ema = (_alpha * hl) + ((1 - _alpha) * _ema); } - _prevEma = ema; + // Add smoothed value to buffer + _smoothed.Add(_ema); + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate rate of change + double roc = ((_ema - _smoothed[_period - 1]) / _smoothed[_period - 1]) * 100; + IsHot = _index >= WarmupPeriod; - return cvi; + return roc; } } diff --git a/lib/volatility/Ewma.cs b/lib/volatility/Ewma.cs new file mode 100644 index 00000000..52fb9443 --- /dev/null +++ b/lib/volatility/Ewma.cs @@ -0,0 +1,141 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// EWMA: Exponential Weighted Moving Average Volatility +/// A volatility measure that gives more weight to recent observations, +/// calculated using squared returns and exponential weighting. +/// +/// +/// The EWMA calculation process: +/// 1. Calculate returns: (Close[t] - Close[t-1])/Close[t-1] +/// 2. Square returns +/// 3. Apply exponential weighting to squared returns +/// 4. Take square root and annualize +/// +/// Key characteristics: +/// - More responsive to recent volatility changes +/// - Default decay factor (lambda) is 0.94 +/// - Default period is 20 days +/// - Annualized by default (multiply by sqrt(252)) +/// - Expressed as a percentage +/// +/// Formula: +/// Returns[t] = (Close[t] - Close[t-1])/Close[t-1] +/// EWMA[t] = λ * EWMA[t-1] + (1-λ) * Returns[t]² +/// Volatility = sqrt(EWMA) * sqrt(252) * 100 +/// +/// Where: +/// λ (lambda) = decay factor (typically 0.94) +/// +/// Market Applications: +/// - Risk measurement +/// - Option pricing +/// - Value at Risk (VaR) +/// - Portfolio optimization +/// - Volatility forecasting +/// +/// Sources: +/// RiskMetrics™ Technical Document (1996) +/// https://www.msci.com/documents/10199/5915b101-4206-4ba0-aee2-3449d5c7e95a +/// +/// Note: Returns annualized volatility as a percentage +/// + +[SkipLocalsInit] +public sealed class Ewma : AbstractBase +{ + private readonly int _period; + private readonly double _lambda; + private readonly bool _annualize; + private double _prevClose; + private double _ewma; + private bool _isInitialized; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ewma(int period = 20, double lambda = 0.94, bool annualize = true) + { + _period = period; + _lambda = lambda; + _annualize = annualize; + WarmupPeriod = period + 1; // Need one extra period for returns + Name = $"EWMA({_period},{_lambda})"; + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ewma(object source, int period = 20, double lambda = 0.94, bool annualize = true) : this(period, lambda, annualize) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _ewma = 0; + _isInitialized = false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate return + double return_ = (BarInput.Close - _prevClose) / _prevClose; + double squaredReturn = return_ * return_; + _prevClose = BarInput.Close; + + // Initialize EWMA if not done + if (!_isInitialized && _index > _period) + { + _ewma = squaredReturn; + _isInitialized = true; + } + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Update EWMA + _ewma = _lambda * _ewma + (1 - _lambda) * squaredReturn; + + // Calculate volatility + double volatility = Math.Sqrt(_ewma); + + // Annualize if requested + if (_annualize) + { + volatility *= Math.Sqrt(252); + } + + // Convert to percentage + volatility *= 100; + + IsHot = _index >= WarmupPeriod; + return volatility; + } +} diff --git a/lib/volatility/Fcb.cs b/lib/volatility/Fcb.cs new file mode 100644 index 00000000..11f99dc1 --- /dev/null +++ b/lib/volatility/Fcb.cs @@ -0,0 +1,163 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// FCB: Fractal Chaos Bands +/// Adaptive price bands based on fractal geometry concepts, +/// identifying potential support and resistance levels. +/// +/// +/// The FCB calculation process: +/// 1. Identify fractal highs and lows over the period +/// 2. Calculate high and low bands using fractal points +/// 3. Smooth bands using exponential moving average +/// +/// Key characteristics: +/// - Adapts to market structure +/// - Default period is 20 days +/// - Default smoothing factor is 0.5 +/// - Returns upper and lower bands +/// - Based on fractal geometry concepts +/// +/// Formula: +/// Fractal High = High[t] where High[t] > High[t±1,2] +/// Fractal Low = Low[t] where Low[t] < Low[t±1,2] +/// Upper Band = EMA(Fractal Highs, smoothing) +/// Lower Band = EMA(Fractal Lows, smoothing) +/// +/// Market Applications: +/// - Support/resistance identification +/// - Trend analysis +/// - Volatility measurement +/// - Breakout detection +/// - Trading range analysis +/// +/// Sources: +/// Bill Williams' Chaos Theory +/// Trading Chaos (2nd Edition) by Bill Williams +/// +/// Note: Returns three values: upper, middle, and lower bands +/// + +[SkipLocalsInit] +public sealed class Fcb : AbstractBase +{ + private readonly int _period; + private readonly double _smoothing; + private readonly CircularBuffer _highs; + private readonly CircularBuffer _lows; + private double _upperBand; + private double _middleBand; + private double _lowerBand; + private double _upperEma; + private double _lowerEma; + private readonly double _alpha; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Fcb(int period = 20, double smoothing = 0.5) + { + _period = period; + _smoothing = smoothing; + _alpha = 2.0 / (_period + 1); + WarmupPeriod = period + 4; // Need extra periods for fractal identification + Name = $"FCB({_period},{_smoothing})"; + _highs = new CircularBuffer(period); + _lows = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Fcb(object source, int period = 20, double smoothing = 0.5) : this(period, smoothing) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _upperBand = 0; + _middleBand = 0; + _lowerBand = 0; + _upperEma = 0; + _lowerEma = 0; + _highs.Clear(); + _lows.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Add current high/low to buffers + _highs.Add(BarInput.High); + _lows.Add(BarInput.Low); + + // Need enough values for calculation + if (_index <= 4) + { + return 0; + } + + // Check for fractal patterns + bool isFractalHigh = false; + bool isFractalLow = false; + + if (_index >= 5) + { + // Fractal high: current high is higher than 2 bars before and after + isFractalHigh = _highs[2] > _highs[0] && _highs[2] > _highs[1] && + _highs[2] > _highs[3] && _highs[2] > _highs[4]; + + // Fractal low: current low is lower than 2 bars before and after + isFractalLow = _lows[2] < _lows[0] && _lows[2] < _lows[1] && + _lows[2] < _lows[3] && _lows[2] < _lows[4]; + } + + // Update EMAs with fractal points + if (isFractalHigh) + { + _upperEma = (_alpha * _highs[2]) + ((1 - _alpha) * _upperEma); + } + if (isFractalLow) + { + _lowerEma = (_alpha * _lows[2]) + ((1 - _alpha) * _lowerEma); + } + + // Apply smoothing to bands + _upperBand = _smoothing * _upperEma + (1 - _smoothing) * BarInput.High; + _lowerBand = _smoothing * _lowerEma + (1 - _smoothing) * BarInput.Low; + _middleBand = (_upperBand + _lowerBand) / 2; + + IsHot = _index >= WarmupPeriod; + return _middleBand; // Return middle band as primary value + } + + /// + /// Gets the upper band value + /// + public double UpperBand => _upperBand; + + /// + /// Gets the middle band value + /// + public double MiddleBand => _middleBand; + + /// + /// Gets the lower band value + /// + public double LowerBand => _lowerBand; +} diff --git a/lib/volatility/Gkv.cs b/lib/volatility/Gkv.cs new file mode 100644 index 00000000..19baebdb --- /dev/null +++ b/lib/volatility/Gkv.cs @@ -0,0 +1,127 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// GKV: Garman-Klass Volatility +/// An efficient estimator of volatility that uses open, high, low, +/// and close prices to capture intraday price movements. +/// +/// +/// The GKV calculation process: +/// 1. Calculate components using OHLC prices +/// 2. Combine components using optimal weights +/// 3. Take rolling average over period +/// 4. Annualize and convert to percentage +/// +/// Key characteristics: +/// - More efficient than close-to-close volatility +/// - Uses full OHLC price information +/// - Default period is 20 days +/// - Annualized by default +/// - Expressed as a percentage +/// +/// Formula: +/// u = ln(High/Low)²/2 +/// c = ln(Close/Open)² +/// GKV = sqrt(sum((0.5*u - (2*ln(2)-1)*c) / period) * 252) * 100 +/// +/// Market Applications: +/// - Volatility estimation +/// - Risk measurement +/// - Option pricing +/// - Trading strategy development +/// - Market analysis +/// +/// Sources: +/// Garman and Klass (1980) +/// Journal of Business 53(1): 67-78 +/// +/// Note: Returns annualized volatility as a percentage +/// + +[SkipLocalsInit] +public sealed class Gkv : AbstractBase +{ + private readonly int _period; + private readonly bool _annualize; + private readonly CircularBuffer _components; + private readonly double _ln2; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Gkv(int period = 20, bool annualize = true) + { + _period = period; + _annualize = annualize; + WarmupPeriod = period; + Name = $"GKV({_period})"; + _components = new CircularBuffer(period); + _ln2 = Math.Log(2); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Gkv(object source, int period = 20, bool annualize = true) : this(period, annualize) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _components.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Calculate components + double u = Math.Log(BarInput.High / BarInput.Low); + u = u * u / 2; + + double c = Math.Log(BarInput.Close / BarInput.Open); + c = c * c; + + // Combine components with optimal weights + double component = 0.5 * u - (2 * _ln2 - 1) * c; + _components.Add(component); + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate average component + double avgComponent = _components.Average(); + + // Calculate volatility + double volatility = Math.Sqrt(avgComponent); + + // Annualize if requested + if (_annualize) + { + volatility *= Math.Sqrt(252); + } + + // Convert to percentage + volatility *= 100; + + IsHot = _index >= WarmupPeriod; + return volatility; + } +} diff --git a/lib/volatility/Hlv.cs b/lib/volatility/Hlv.cs new file mode 100644 index 00000000..63a0b3ea --- /dev/null +++ b/lib/volatility/Hlv.cs @@ -0,0 +1,130 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// HLV: High-Low Volatility +/// A volatility measure based on the high-low range relative +/// to the previous close, capturing intraday price movements. +/// +/// +/// The HLV calculation process: +/// 1. Calculate normalized high-low range +/// 2. Take rolling average over period +/// 3. Convert to annualized volatility +/// +/// Key characteristics: +/// - Captures intraday price movements +/// - Uses high, low, and previous close +/// - Default period is 20 days +/// - Annualized by default +/// - Expressed as a percentage +/// +/// Formula: +/// Range = (High - Low) / PrevClose +/// HLV = sqrt(sum(Range² / period) * 252) * 100 +/// +/// Market Applications: +/// - Volatility measurement +/// - Risk assessment +/// - Trading range analysis +/// - Market regime identification +/// - Position sizing +/// +/// Sources: +/// Parkinson (1980) modified +/// The Extreme Value Method for Estimating the Variance of the Rate of Return +/// Journal of Business 53(1): 61-65 +/// +/// Note: Returns annualized volatility as a percentage +/// + +[SkipLocalsInit] +public sealed class Hlv : AbstractBase +{ + private readonly int _period; + private readonly bool _annualize; + private readonly CircularBuffer _ranges; + private double _prevClose; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Hlv(int period = 20, bool annualize = true) + { + _period = period; + _annualize = annualize; + WarmupPeriod = period + 1; // Need one extra period for previous close + Name = $"HLV({_period})"; + _ranges = new CircularBuffer(period); + Init(); + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Hlv(object source, int period = 20, bool annualize = true) : this(period, annualize) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _prevClose = 0; + _ranges.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _lastValidValue = Value; + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + // Skip first period to establish previous close + if (_index == 1) + { + _prevClose = BarInput.Close; + return 0; + } + + // Calculate normalized range + double range = (BarInput.High - BarInput.Low) / _prevClose; + double squaredRange = range * range; + _ranges.Add(squaredRange); + + // Store current close for next calculation + _prevClose = BarInput.Close; + + // Need enough values for calculation + if (_index <= _period) + { + return 0; + } + + // Calculate average squared range + double avgSquaredRange = _ranges.Average(); + + // Calculate volatility + double volatility = Math.Sqrt(avgSquaredRange); + + // Annualize if requested + if (_annualize) + { + volatility *= Math.Sqrt(252); + } + + // Convert to percentage + volatility *= 100; + + IsHot = _index >= WarmupPeriod; + return volatility; + } +} diff --git a/lib/volatility/_list.md b/lib/volatility/_list.md index 49854141..9e0f4094 100644 --- a/lib/volatility/_list.md +++ b/lib/volatility/_list.md @@ -1,21 +1,21 @@ # Volatility indicators -Done: 15, Todo: 20 +Done: 24, Todo: 11 ✔️ ADR - Average Daily Range ✔️ AP - Andrew's Pitchfork ✔️ ATR - Average True Range ✔️ ATRP - Average True Range Percent ✔️ ATRS - ATR Trailing Stop -*BB - Bollinger Bands® (Upper, Middle, Lower) -CCV - Close-to-Close Volatility -CE - Chandelier Exit -CV - Conditional Volatility (ARCH/GARCH) -CVI - Chaikin's Volatility +✔️ BBAND - Bollinger Bands® (Upper, Middle, Lower) +✔️ CCV - Close-to-Close Volatility +✔️ CE - Chandelier Exit +✔️ CV - Conditional Volatility (ARCH/GARCH) +✔️ CVI - Chaikin's Volatility *DC - Donchian Channels (Upper, Middle, Lower) -EWMA - Exponential Weighted Moving Average Volatility -FCB - Fractal Chaos Bands -GKV - Garman-Klass Volatility -HLV - High-Low Volatility +✔️ EWMA - Exponential Weighted Moving Average Volatility +✔️ FCB - Fractal Chaos Bands +✔️ GKV - Garman-Klass Volatility +✔️ HLV - High-Low Volatility ✔️ HV - Historical Volatility *ICH - Ichimoku Cloud (Conversion, Base, Leading Span A, Leading Span B, Lagging Span) ✔️ JVOLTY - Jurik Volatility diff --git a/quantower/Averages/_Averages.csproj b/quantower/Averages/_Averages.csproj index 651c18c5..ea03d5a8 100644 --- a/quantower/Averages/_Averages.csproj +++ b/quantower/Averages/_Averages.csproj @@ -2,13 +2,10 @@ Averages Indicator - 0.0.0.1 bin\$(Configuration)\ - true - true - true false + @@ -16,7 +13,6 @@ - ..\..\.github\TradingPlatform.BusinessLayer.dll @@ -32,4 +28,4 @@ DestinationFolder="$(QuantowerRoot)\Settings\Scripts\Indicators\QuanTAlib\Averages" /> - + \ No newline at end of file diff --git a/quantower/Momentum/_Momentum.csproj b/quantower/Momentum/_Momentum.csproj index e249d4a8..29adbe09 100644 --- a/quantower/Momentum/_Momentum.csproj +++ b/quantower/Momentum/_Momentum.csproj @@ -2,13 +2,10 @@ Momentum Indicator - 0.0.0.1 bin\$(Configuration)\ - true - true - true false + @@ -30,4 +27,4 @@ - + \ No newline at end of file diff --git a/quantower/Oscillators/_Oscillators.csproj b/quantower/Oscillators/_Oscillators.csproj index 6b20b9df..11c13ec8 100644 --- a/quantower/Oscillators/_Oscillators.csproj +++ b/quantower/Oscillators/_Oscillators.csproj @@ -2,13 +2,10 @@ Oscillators Indicator - 0.0.0.1 bin\$(Configuration)\ - true - true - true false + @@ -30,4 +27,4 @@ - + \ No newline at end of file diff --git a/quantower/Statistics/_Statistics.csproj b/quantower/Statistics/_Statistics.csproj index d78a01e2..f67a5d55 100644 --- a/quantower/Statistics/_Statistics.csproj +++ b/quantower/Statistics/_Statistics.csproj @@ -2,13 +2,10 @@ Statistics Indicator - 0.0.0.1 bin\$(Configuration)\ - true - true - true false + diff --git a/quantower/Volatility/_Volatility.csproj b/quantower/Volatility/_Volatility.csproj index eefb1d1a..2612c385 100644 --- a/quantower/Volatility/_Volatility.csproj +++ b/quantower/Volatility/_Volatility.csproj @@ -2,13 +2,10 @@ Volatility Indicator - 0.0.0.1 bin\$(Configuration)\ - true - true - true false + @@ -16,7 +13,6 @@ - ..\..\.github\TradingPlatform.BusinessLayer.dll @@ -31,4 +27,4 @@ - + \ No newline at end of file diff --git a/quantower/Volume/_Volume.csproj b/quantower/Volume/_Volume.csproj index 281f89b3..db6a0d69 100644 --- a/quantower/Volume/_Volume.csproj +++ b/quantower/Volume/_Volume.csproj @@ -2,13 +2,10 @@ Volume Indicator - 0.0.0.1 bin\$(Configuration)\ - true - true - true false + @@ -30,4 +27,4 @@ - + \ No newline at end of file