diff --git a/Tests/test_updates_momentum.cs b/Tests/test_updates_momentum.cs index a94d2386..3ca93dcc 100644 --- a/Tests/test_updates_momentum.cs +++ b/Tests/test_updates_momentum.cs @@ -88,4 +88,129 @@ public class MomentumUpdateTests Assert.Equal(initialValue, finalValue, precision); } + + [Fact] + public void Dmx_Update() + { + var indicator = new Dmx(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 Pmo_Update() + { + var indicator = new Pmo(period1: 35, period2: 20); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Po_Update() + { + var indicator = new Po(fastPeriod: 10, slowPeriod: 21); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Ppo_Update() + { + var indicator = new Ppo(fastPeriod: 12, slowPeriod: 26); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Prs_Update() + { + var indicator = new Prs(); + indicator.SetBenchmark(ReferenceValue); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.SetBenchmark(GetRandomDouble() + 100); // Ensure positive benchmark + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + + indicator.SetBenchmark(ReferenceValue); + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Roc_Update() + { + var indicator = new Roc(period: 12); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble() + 100, IsNew: false)); // Ensure positive prices + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Mom_Update() + { + var indicator = new Mom(period: 10); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } + + [Fact] + public void Vel_Update() + { + var indicator = new Vel(period: 10); + double initialValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true)); + + for (int i = 0; i < RandomUpdates; i++) + { + indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false)); + } + double finalValue = indicator.Calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false)); + + Assert.Equal(initialValue, finalValue, precision); + } } diff --git a/lib/momentum/Dmx.cs b/lib/momentum/Dmx.cs new file mode 100644 index 00000000..3f70113f --- /dev/null +++ b/lib/momentum/Dmx.cs @@ -0,0 +1,174 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// DMX: Enhanced Directional Movement Index using JMA smoothing +/// An improvement over the traditional DMI indicator that uses Jurik Moving Average (JMA) +/// for smoothing instead of Wilder's moving average. This enhancement provides better +/// noise reduction while maintaining responsiveness to significant price movements. +/// +/// +/// The DMX calculation process: +/// 1. Calculate True Range (TR) +/// 2. Calculate +DM (Positive Directional Movement) +/// 3. Calculate -DM (Negative Directional Movement) +/// 4. Smooth TR, +DM, and -DM using JMA instead of Wilder's smoothing +/// 5. Calculate +DI and -DI as percentages +/// +/// Key improvements over DMI: +/// - Uses JMA's adaptive volatility-based smoothing +/// - Better noise reduction in the directional movement signals +/// - Maintains responsiveness to significant price movements +/// - Reduced lag through JMA's phase-shifting +/// +/// Formula: +/// TR = max(high-low, abs(high-prevClose), abs(low-prevClose)) +/// +DM = if(high-prevHigh > prevLow-low) then max(high-prevHigh, 0) else 0 +/// -DM = if(prevLow-low > high-prevHigh) then max(prevLow-low, 0) else 0 +/// +DI = 100 * JMA(+DM) / JMA(TR) +/// -DI = 100 * JMA(-DM) / JMA(TR) +/// +/// Sources: +/// Original DMI by J. Welles Wilder Jr. - "New Concepts in Technical Trading Systems" (1978) +/// Enhanced with JMA smoothing by Mark Jurik +/// + +[SkipLocalsInit] +public sealed class Dmx : AbstractBarBase +{ + private readonly Jma _smoothedTr; + private readonly Jma _smoothedPlusDm; + private readonly Jma _smoothedMinusDm; + private double _prevHigh, _prevLow, _prevClose; + private double _p_prevHigh, _p_prevLow, _p_prevClose; + private double _plusDi, _minusDi; + private const double ScalingFactor = 100.0; + private const int DefaultPeriod = 10; + private const int DefaultPhase = 100; + private const double DefaultFactor = 0.25; + + /// + /// Gets the most recent +DI value + /// + public double PlusDI => _plusDi; + + /// + /// Gets the most recent -DI value + /// + public double MinusDI => _minusDi; + + /// The number of periods used in the DMX calculation (default 14). + /// The phase for the JMA smoothing (default 0). + /// The factor for the JMA smoothing (default 0.45). + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Dmx(int period = DefaultPeriod, int phase = DefaultPhase, double factor = DefaultFactor) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + _smoothedTr = new(period, phase, factor); + _smoothedPlusDm = new(period, phase, factor); + _smoothedMinusDm = new(period, phase, factor); + _index = 0; + WarmupPeriod = period * 2; // JMA needs more warmup periods than RMA + Name = $"DMX({period})"; + } + + /// The data source object that publishes updates. + /// The number of periods used in the DMX calculation. + /// The phase for the JMA smoothing. + /// The factor for the JMA smoothing. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Dmx(object source, int period, int phase = DefaultPhase, double factor = DefaultFactor) : this(period, phase, factor) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new BarSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevHigh = _prevHigh; + _p_prevLow = _prevLow; + _p_prevClose = _prevClose; + } + else + { + _prevHigh = _p_prevHigh; + _prevLow = _p_prevLow; + _prevClose = _p_prevClose; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static double CalculateTrueRange(double high, double low, double prevClose) + { + double hl = high - low; + double hpc = Math.Abs(high - prevClose); + double lpc = Math.Abs(low - prevClose); + return Math.Max(hl, Math.Max(hpc, lpc)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static (double plusDm, double minusDm) CalculateDirectionalMovement( + double high, double low, double prevHigh, double prevLow) + { + double upMove = high - prevHigh; + double downMove = prevLow - low; + + double plusDm = 0.0; + double minusDm = 0.0; + + if (upMove > downMove && upMove > 0) + plusDm = upMove; + else if (downMove > upMove && downMove > 0) + minusDm = downMove; + + return (plusDm, minusDm); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_index == 1) + { + _prevHigh = Input.High; + _prevLow = Input.Low; + _prevClose = Input.Close; + return 0.0; + } + + // Calculate True Range and Directional Movement + double tr = CalculateTrueRange(Input.High, Input.Low, _prevClose); + var (plusDm, minusDm) = CalculateDirectionalMovement( + Input.High, Input.Low, _prevHigh, _prevLow); + + // Update previous values + _prevHigh = Input.High; + _prevLow = Input.Low; + _prevClose = Input.Close; + + // Smooth the indicators using JMA + _smoothedTr.Calc(tr, Input.IsNew); + _smoothedPlusDm.Calc(plusDm, Input.IsNew); + _smoothedMinusDm.Calc(minusDm, Input.IsNew); + + // Calculate +DI and -DI + double smoothedTr = _smoothedTr.Value; + if (smoothedTr > 0) + { + _plusDi = ScalingFactor * _smoothedPlusDm.Value / smoothedTr; + _minusDi = ScalingFactor * _smoothedMinusDm.Value / smoothedTr; + return _plusDi - _minusDi; // Return the difference as main value + } + + _plusDi = 0.0; + _minusDi = 0.0; + return 0.0; + } +} diff --git a/lib/momentum/Mom.cs b/lib/momentum/Mom.cs new file mode 100644 index 00000000..56e86f33 --- /dev/null +++ b/lib/momentum/Mom.cs @@ -0,0 +1,75 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// Mom: Momentum +/// A basic momentum indicator that measures the change in price over a specified +/// period, helping identify the strength and speed of price movements. +/// +/// +/// The Momentum calculation process: +/// 1. Store historical prices in a circular buffer +/// 2. Calculate absolute difference between current and historical price +/// 3. No scaling factor applied to maintain raw price difference +/// +/// Key characteristics: +/// - Basic momentum measurement +/// - Shows absolute price changes +/// - Zero line crossovers signal trend changes +/// - Foundation for other momentum indicators +/// +/// Formula: +/// Mom = Price - PriceN +/// where PriceN is the price N periods ago +/// +/// Sources: +/// Technical Analysis of Financial Markets by John J. Murphy +/// Technical Analysis Using Multiple Timeframes by Brian Shannon +/// + +[SkipLocalsInit] +public sealed class Mom : AbstractBase +{ + private readonly CircularBuffer _priceBuffer; + private const int DefaultPeriod = 10; + + /// The lookback period for momentum calculation (default 10). + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Mom(int period = DefaultPeriod) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + + _priceBuffer = new(period + 1); + WarmupPeriod = period; + Name = $"MOM({period})"; + } + + /// The data source object that publishes updates. + /// The lookback period for momentum calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Mom(object source, int period) : this(period) + { + 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; + + return Input.Value - _priceBuffer[0]; + } +} diff --git a/lib/momentum/Pmo.cs b/lib/momentum/Pmo.cs new file mode 100644 index 00000000..cd22df83 --- /dev/null +++ b/lib/momentum/Pmo.cs @@ -0,0 +1,102 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PMO: Price Momentum Oscillator +/// A momentum indicator that uses exponential moving averages of ROC (Rate of Change) +/// to identify overbought and oversold conditions in price movements. +/// +/// +/// The PMO calculation process: +/// 1. Calculate ROC (Rate of Change) of closing prices +/// 2. Apply a first smoothing EMA to the ROC values +/// 3. Apply a second smoothing EMA to the result +/// 4. Multiply by a scaling factor for better visualization +/// +/// Key characteristics: +/// - Double-smoothed momentum indicator +/// - Helps identify overbought/oversold conditions +/// - Useful for trend confirmation and divergence analysis +/// - More responsive than traditional momentum oscillators +/// +/// Formula: +/// ROC = (Close - PrevClose) / PrevClose +/// Signal1 = EMA(ROC, Period1) +/// PMO = EMA(Signal1, Period2) * ScalingFactor +/// +/// Sources: +/// Developed by Carl Swenlin +/// Technical Analysis of Stocks and Commodities magazine +/// + +[SkipLocalsInit] +public sealed class Pmo : AbstractBase +{ + private readonly Ema _smoothing1; + private readonly Ema _smoothing2; + private double _prevClose; + private double _p_prevClose; + private const double ScalingFactor = 100.0; + private const int DefaultPeriod1 = 35; + private const int DefaultPeriod2 = 20; + + /// The first smoothing period (default 35). + /// The second smoothing period (default 20). + /// Thrown when either period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pmo(int period1 = DefaultPeriod1, int period2 = DefaultPeriod2) + { + if (period1 < 1 || period2 < 1) + throw new ArgumentOutOfRangeException(nameof(period1)); + + _smoothing1 = new(period1); + _smoothing2 = new(period2); + _index = 0; + WarmupPeriod = period1 + period2; + Name = $"PMO({period1},{period2})"; + } + + /// The data source object that publishes updates. + /// The first smoothing period. + /// The second smoothing period. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Pmo(object source, int period1, int period2) : this(period1, period2) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + { + _index++; + _p_prevClose = _prevClose; + } + else + { + _prevClose = _p_prevClose; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_index == 1) + { + _prevClose = Input.Value; + return 0.0; + } + + // Calculate Rate of Change + double roc = (Input.Value - _prevClose) / _prevClose; + _prevClose = Input.Value; + + // Apply double smoothing + double signal1 = _smoothing1.Calc(roc, Input.IsNew); + return _smoothing2.Calc(signal1, Input.IsNew) * ScalingFactor; + } +} diff --git a/lib/momentum/Po.cs b/lib/momentum/Po.cs new file mode 100644 index 00000000..170096bd --- /dev/null +++ b/lib/momentum/Po.cs @@ -0,0 +1,80 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PO: Price Oscillator +/// A momentum indicator that measures the difference between two moving averages +/// of different periods to identify price momentum and potential trend changes. +/// +/// +/// The PO calculation process: +/// 1. Calculate fast EMA of closing prices +/// 2. Calculate slow EMA of closing prices +/// 3. Calculate the difference between fast and slow EMAs +/// 4. Multiply by a scaling factor for better visualization +/// +/// Key characteristics: +/// - Measures momentum through moving average differences +/// - Helps identify trend direction and potential reversals +/// - Zero line crossovers signal trend changes +/// - Similar to MACD but more customizable periods +/// +/// Formula: +/// FastMA = EMA(Close, FastPeriod) +/// SlowMA = EMA(Close, SlowPeriod) +/// PO = (FastMA - SlowMA) * ScalingFactor +/// +/// Sources: +/// Technical Analysis of Financial Markets by John J. Murphy +/// + +[SkipLocalsInit] +public sealed class Po : AbstractBase +{ + private readonly Ema _fastEma; + private readonly Ema _slowEma; + private const double ScalingFactor = 1.0; + private const int DefaultFastPeriod = 10; + private const int DefaultSlowPeriod = 21; + + /// The fast EMA period (default 10). + /// The slow EMA period (default 21). + /// Thrown when either period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Po(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod) + { + if (fastPeriod < 1 || slowPeriod < 1) + throw new ArgumentOutOfRangeException(nameof(fastPeriod)); + if (fastPeriod >= slowPeriod) + throw new ArgumentException("Fast period must be less than slow period"); + + _fastEma = new(fastPeriod); + _slowEma = new(slowPeriod); + WarmupPeriod = slowPeriod; + Name = $"PO({fastPeriod},{slowPeriod})"; + } + + /// The data source object that publishes updates. + /// The fast EMA period. + /// The slow EMA period. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Po(object source, int fastPeriod, int slowPeriod) : this(fastPeriod, slowPeriod) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + // No state management needed for this indicator + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + double fastEma = _fastEma.Calc(Input.Value, Input.IsNew); + double slowEma = _slowEma.Calc(Input.Value, Input.IsNew); + return (fastEma - slowEma) * ScalingFactor; + } +} diff --git a/lib/momentum/Ppo.cs b/lib/momentum/Ppo.cs new file mode 100644 index 00000000..2c46c9b8 --- /dev/null +++ b/lib/momentum/Ppo.cs @@ -0,0 +1,85 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PPO: Percentage Price Oscillator +/// A momentum indicator that shows the percentage difference between two moving averages +/// of different periods, helping identify price momentum and potential trend changes. +/// +/// +/// The PPO calculation process: +/// 1. Calculate fast EMA of closing prices +/// 2. Calculate slow EMA of closing prices +/// 3. Calculate the percentage difference between fast and slow EMAs +/// 4. Multiply by a scaling factor for better visualization +/// +/// Key characteristics: +/// - Measures momentum through percentage differences +/// - Normalized for comparison across different price levels +/// - Zero line crossovers signal trend changes +/// - Similar to MACD but expressed as a percentage +/// +/// Formula: +/// FastMA = EMA(Close, FastPeriod) +/// SlowMA = EMA(Close, SlowPeriod) +/// PPO = ((FastMA - SlowMA) / SlowMA) * 100 +/// +/// Sources: +/// Technical Analysis of Financial Markets by John J. Murphy +/// StockCharts.com Technical Indicators +/// + +[SkipLocalsInit] +public sealed class Ppo : AbstractBase +{ + private readonly Ema _fastEma; + private readonly Ema _slowEma; + private const double ScalingFactor = 100.0; + private const int DefaultFastPeriod = 12; + private const int DefaultSlowPeriod = 26; + + /// The fast EMA period (default 12). + /// The slow EMA period (default 26). + /// Thrown when either period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ppo(int fastPeriod = DefaultFastPeriod, int slowPeriod = DefaultSlowPeriod) + { + if (fastPeriod < 1 || slowPeriod < 1) + throw new ArgumentOutOfRangeException(nameof(fastPeriod)); + if (fastPeriod >= slowPeriod) + throw new ArgumentException("Fast period must be less than slow period"); + + _fastEma = new(fastPeriod); + _slowEma = new(slowPeriod); + WarmupPeriod = slowPeriod; + Name = $"PPO({fastPeriod},{slowPeriod})"; + } + + /// The data source object that publishes updates. + /// The fast EMA period. + /// The slow EMA period. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Ppo(object source, int fastPeriod, int slowPeriod) : this(fastPeriod, slowPeriod) + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + // No state management needed for this indicator + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + double fastEma = _fastEma.Calc(Input.Value, Input.IsNew); + double slowEma = _slowEma.Calc(Input.Value, Input.IsNew); + + if (Math.Abs(slowEma) <= double.Epsilon) + return 0.0; + + return ((fastEma - slowEma) / slowEma) * ScalingFactor; + } +} diff --git a/lib/momentum/Prs.cs b/lib/momentum/Prs.cs new file mode 100644 index 00000000..f2bbb682 --- /dev/null +++ b/lib/momentum/Prs.cs @@ -0,0 +1,84 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// PRS: Price Relative Strength +/// A momentum indicator that compares the performance of a security against a benchmark, +/// helping identify which is showing stronger relative momentum. +/// +/// +/// The PRS calculation process: +/// 1. Take the current price of the security +/// 2. Take the current price of the benchmark +/// 3. Calculate the ratio between them +/// 4. Multiply by a scaling factor for better visualization +/// +/// Key characteristics: +/// - Measures relative performance against a benchmark +/// - Helps identify market leaders and laggards +/// - Rising PRS indicates outperformance +/// - Falling PRS indicates underperformance +/// +/// Formula: +/// PRS = (Price / Benchmark) * 100 +/// +/// Sources: +/// Technical Analysis of Financial Markets by John J. Murphy +/// StockCharts.com Technical Indicators +/// + +[SkipLocalsInit] +public sealed class Prs : AbstractBase +{ + private const double ScalingFactor = 100.0; + private double _benchmark; + private double _p_benchmark; + + /// + /// Initializes a new instance of the PRS indicator + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Prs() + { + WarmupPeriod = 1; + Name = "PRS"; + } + + /// The data source object that publishes updates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Prs(object source) : this() + { + var pubEvent = source.GetType().GetEvent("Pub"); + pubEvent?.AddEventHandler(source, new ValueSignal(Sub)); + } + + /// + /// Sets the current benchmark value + /// + /// The benchmark value to compare against + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetBenchmark(double benchmark) + { + _benchmark = benchmark; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override void ManageState(bool isNew) + { + if (isNew) + _p_benchmark = _benchmark; + else + _benchmark = _p_benchmark; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override double Calculation() + { + ManageState(Input.IsNew); + + if (_benchmark <= double.Epsilon) + return 0.0; + + return (Input.Value / _benchmark) * ScalingFactor; + } +} diff --git a/lib/momentum/Roc.cs b/lib/momentum/Roc.cs new file mode 100644 index 00000000..f61070ec --- /dev/null +++ b/lib/momentum/Roc.cs @@ -0,0 +1,80 @@ +using System.Runtime.CompilerServices; +namespace QuanTAlib; + +/// +/// ROC: Rate of Change +/// A momentum indicator that measures the percentage change in price over a specified +/// period, helping identify the speed and strength of price movements. +/// +/// +/// The ROC calculation process: +/// 1. Store historical prices in a circular buffer +/// 2. Calculate percentage change between current and historical price +/// 3. Multiply by scaling factor for better visualization +/// +/// Key characteristics: +/// - Pure momentum indicator +/// - Oscillates around zero line +/// - Helps identify overbought/oversold conditions +/// - Useful for divergence analysis +/// +/// Formula: +/// ROC = ((Price - PriceN) / PriceN) * 100 +/// where PriceN is the price N periods ago +/// +/// Sources: +/// Technical Analysis of Financial Markets by John J. Murphy +/// Technical Analysis of Stock Trends by Robert D. Edwards and John Magee +/// + +[SkipLocalsInit] +public sealed class Roc : AbstractBase +{ + private readonly CircularBuffer _priceBuffer; + private const double ScalingFactor = 100.0; + private const int DefaultPeriod = 12; + + /// The lookback period for ROC calculation (default 12). + /// Thrown when period is less than 1. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Roc(int period = DefaultPeriod) + { + if (period < 1) + throw new ArgumentOutOfRangeException(nameof(period)); + + _priceBuffer = new(period + 1); + WarmupPeriod = period; + Name = $"ROC({period})"; + } + + /// The data source object that publishes updates. + /// The lookback period for ROC calculation. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Roc(object source, int period) : this(period) + { + 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; + + double oldPrice = _priceBuffer[0]; + if (oldPrice <= double.Epsilon) + return 0.0; + + return ((Input.Value - oldPrice) / oldPrice) * ScalingFactor; + } +} diff --git a/lib/momentum/Vel.cs b/lib/momentum/Vel.cs new file mode 100644 index 00000000..c0a8472e --- /dev/null +++ b/lib/momentum/Vel.cs @@ -0,0 +1,89 @@ +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); + } +} diff --git a/lib/momentum/_list.md b/lib/momentum/_list.md index 89739c87..1928a3d8 100644 --- a/lib/momentum/_list.md +++ b/lib/momentum/_list.md @@ -3,16 +3,16 @@ ✔️ ADX - Average Directional Movement Index ✔️ ADXR - Average Directional Movement Index Rating ✔️ APO - Absolute Price Oscillator -DMI - Directional Movement Index -DMX - Jurik Directional Movement Index +✔️ DMI - Directional Movement Index +✔️ DMX - Jurik Directional Movement Index DPO - Detrended Price Oscillator MACD - Moving Average Convergence/Divergence -MOM - Momentum -PMO - Price Momentum Oscillator -PO - Price Oscillator -PPO - Percentage Price Oscillator -PRS - Price Relative Strength -ROC - Rate of Change +✔️ MOM - Momentum +✔️ PMO - Price Momentum Oscillator +✔️ PO - Price Oscillator +✔️ PPO - Percentage Price Oscillator +✔️ PRS - Price Relative Strength +✔️ ROC - Rate of Change TRIX - 1-day ROC of TEMA -VEL - Jurik Signal Velocity +✔️ VEL - Jurik Signal Velocity VORTEX - Vortex Indicator