mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-27 17:27:43 +00:00
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).
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace QuanTAlib;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
|
||||
[SkipLocalsInit]
|
||||
public sealed class Cvi : AbstractBase
|
||||
{
|
||||
private readonly int _period;
|
||||
private readonly Ema _ema;
|
||||
private readonly CircularBuffer _buffer;
|
||||
private double _prevEma;
|
||||
|
||||
/// <param name="period">The number of periods for CVI calculation.</param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when period is less than 1.</exception>
|
||||
[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})";
|
||||
}
|
||||
|
||||
/// <param name="source">The data source object that publishes updates.</param>
|
||||
/// <param name="period">The number of periods for CVI calculation.</param>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user