using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// Vel: Velocity /// An enhanced momentum indicator that applies Jurik Moving Average (JMA) smoothing /// to the basic momentum calculation, providing better noise reduction while /// maintaining responsiveness to significant price movements. /// /// /// The Velocity calculation process: /// 1. Calculate basic momentum (price difference) /// 2. Apply JMA smoothing to the momentum values /// 3. No scaling factor applied to maintain price-based units /// /// Key characteristics: /// - Enhanced momentum measurement with JMA smoothing /// - Better noise reduction than basic momentum /// - Maintains responsiveness to significant moves /// - Reduced lag through JMA's phase-shifting /// /// Formula: /// Mom = Price - PriceN /// Vel = JMA(Mom, period) /// /// Sources: /// Enhanced with JMA smoothing by Mark Jurik /// Technical Analysis of Financial Markets by John J. Murphy /// [SkipLocalsInit] public sealed class Vel : AbstractBase { private readonly CircularBuffer _priceBuffer; private readonly Jma _smoothing; private const int DefaultPeriod = 10; private const int DefaultPhase = 100; private const double DefaultFactor = 0.25; /// The lookback period for velocity calculation (default 10). /// The phase for the JMA smoothing (default 0). /// The power factor for the JMA smoothing (default 2.0). /// Thrown when period is less than 1. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vel(int period = DefaultPeriod, int phase = DefaultPhase, double factor = DefaultFactor) { if (period < 1) throw new ArgumentOutOfRangeException(nameof(period)); _priceBuffer = new(period + 1); _smoothing = new(period, phase, factor); WarmupPeriod = period * 2; // JMA needs more warmup periods Name = $"VEL({period})"; } /// The data source object that publishes updates. /// The lookback period for velocity calculation. /// The phase for the JMA smoothing. /// The power factor for the JMA smoothing. [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vel(object source, int period, int phase = DefaultPhase, double power = DefaultFactor) : this(period, phase, power) { var pubEvent = source.GetType().GetEvent("Pub"); pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void ManageState(bool isNew) { if (isNew) _priceBuffer.Add(Input.Value); } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] protected override double Calculation() { ManageState(Input.IsNew); if (_priceBuffer.Count < _priceBuffer.Capacity) return 0.0; // Calculate basic momentum double momentum = Input.Value - _priceBuffer[0]; // Apply JMA smoothing return _smoothing.Calc(momentum, Input.IsNew); } }