From f55f630ac60b98f1edec8914cf006412348eec01 Mon Sep 17 00:00:00 2001 From: Miha Kralj <31756078+mihakralj@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:19:40 -0700 Subject: [PATCH] Add CVI - Chaikin's Volatility class and tests Add implementation of CVI - Chaikin's Volatility class and related tests. * **Cvi Class Implementation:** - Add `Cvi` class in `lib/volatility/Cvi.cs` to calculate Chaikin's Volatility. - Use high and low prices for calculation. - Include a constructor with `period` parameter. - Add a method to calculate Chaikin's Volatility. * **Quantower Indicator:** - Add `CviIndicator` class in `quantower/Volatility/CviIndicator.cs`. - Use `Cvi` class for calculation. - Add input parameters for `period` and `showColdValues`. - Implement `OnInit` and `OnUpdate` methods. * **Tests:** - Add a test method for `Cvi` class in `Tests/test_updates_volatility.cs`. - Use random updates to test `Cvi`. - Ensure initial and final values are equal. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/mihakralj/QuanTAlib?shareId=XXXX-XXXX-XXXX-XXXX). --- Tests/test_updates_volatility.cs | 16 ++++ lib/volatility/Cvi.cs | 109 +++++++++++++++++++++++++++ quantower/Volatility/CviIndicator.cs | 54 +++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 lib/volatility/Cvi.cs create mode 100644 quantower/Volatility/CviIndicator.cs diff --git a/Tests/test_updates_volatility.cs b/Tests/test_updates_volatility.cs index f8877a8e..05d43c60 100644 --- a/Tests/test_updates_volatility.cs +++ b/Tests/test_updates_volatility.cs @@ -101,4 +101,20 @@ 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); + } } diff --git a/lib/volatility/Cvi.cs b/lib/volatility/Cvi.cs new file mode 100644 index 00000000..a6895321 --- /dev/null +++ b/lib/volatility/Cvi.cs @@ -0,0 +1,109 @@ +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. +/// +/// +/// 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. +/// +/// Key characteristics: +/// - Measures volatility +/// - Uses high and low prices +/// - Percentage-based +/// - EMA smoothing +/// +/// Formula: +/// CVI = (EMA(high - low, period) - EMA(high - low, period, offset)) / EMA(high - low, period, offset) * 100 +/// +/// Market Applications: +/// - Volatility assessment +/// - Trend confirmation +/// - Risk management +/// - Entry/exit timing +/// +/// Sources: +/// Marc Chaikin - Original development +/// https://www.investopedia.com/terms/c/chaikins-volatility.asp +/// +/// Note: Higher CVI values indicate higher volatility +/// + +[SkipLocalsInit] +public sealed class Cvi : AbstractBase +{ + private readonly int _period; + private readonly Ema _ema; + private readonly CircularBuffer _buffer; + private double _prevEma; + + /// The number of periods for CVI calculation. + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Cvi(int period) + { + 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})"; + } + + /// 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) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Init() + { + base.Init(); + _ema.Init(); + _buffer.Clear(); + _prevEma = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(BarInput.IsNew); + + double highLowDiff = BarInput.High - BarInput.Low; + _buffer.Add(highLowDiff, BarInput.IsNew); + + double ema = _ema.Calc(new TValue(Input.Time, highLowDiff, BarInput.IsNew)).Value; + + double cvi = 0; + if (_index >= _period) + { + double prevEma = _buffer[_buffer.Count - _period]; + cvi = (ema - prevEma) / prevEma * 100; + } + + _prevEma = ema; + IsHot = _index >= WarmupPeriod; + return cvi; + } +} diff --git a/quantower/Volatility/CviIndicator.cs b/quantower/Volatility/CviIndicator.cs new file mode 100644 index 00000000..7a43523f --- /dev/null +++ b/quantower/Volatility/CviIndicator.cs @@ -0,0 +1,54 @@ +using System.Drawing; +using TradingPlatform.BusinessLayer; + +namespace QuanTAlib; + +public class CviIndicator : Indicator, IWatchlistIndicator +{ + [InputParameter("Periods", sortIndex: 1, 1, 2000, 1, 0)] + public int Periods { get; set; } = 20; + + [InputParameter("Show cold values", sortIndex: 21)] + public bool ShowColdValues { get; set; } = true; + + private Cvi? cvi; + protected LineSeries? CviSeries; + public int MinHistoryDepths => Math.Max(5, Periods * 2); + int IWatchlistIndicator.MinHistoryDepths => MinHistoryDepths; + + public CviIndicator() + { + Name = "CVI - Chaikin's Volatility"; + Description = "Measures the volatility of a financial instrument by comparing the spread between the high and low prices."; + SeparateWindow = true; + + CviSeries = new($"CVI {Periods}", Color.Blue, 2, LineStyle.Solid); + AddLineSeries(CviSeries); + } + + protected override void OnInit() + { + cvi = new Cvi(Periods); + base.OnInit(); + } + + protected override void OnUpdate(UpdateArgs args) + { + TBar input = IndicatorExtensions.GetInputBar(this, args); + TValue result = cvi!.Calc(input); + + CviSeries!.SetValue(result.Value); + CviSeries!.SetMarker(0, Color.Transparent); //OnPaintChart draws the line, hidden here + } + +#pragma warning disable CA1416 // Validate platform compatibility + + public override string ShortName => $"CVI ({Periods})"; + + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + this.PaintHLine(args, 0.05, new Pen(Color.DarkRed, width: 2)); + this.PaintSmoothCurve(args, CviSeries!, cvi!.WarmupPeriod, showColdValues: ShowColdValues, tension: 0.2); + } +}