using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// ADR: Average Daily Range /// /// /// Smoothed average of High-Low ranges; simpler than ATR (no gap accounting). /// Supports SMA/EMA/WMA smoothing methods. /// /// Calculation: ADR = MA(High - Low, period). /// /// Detailed documentation [SkipLocalsInit] public sealed class Adr : AbstractBase { private readonly AbstractBase _ma; private ITValuePublisher? _source; private bool _disposed; /// /// Creates ADR with specified period and smoothing method. /// /// Period for ADR calculation (must be > 0) /// Smoothing method (default: SMA) public Adr(int period, AdrMethod method = AdrMethod.Sma) { if (period <= 0) { throw new ArgumentException("Period must be greater than 0", nameof(period)); } _ma = method switch { AdrMethod.Sma => new Sma(period), AdrMethod.Ema => new Ema(period), AdrMethod.Wma => new Wma(period), _ => throw new ArgumentException($"Invalid smoothing method: {method}", nameof(method)) }; Name = $"Adr({period},{method})"; WarmupPeriod = _ma.WarmupPeriod; } /// /// Creates ADR with specified source, period, and smoothing method. /// /// Source to subscribe to /// Period for ADR calculation /// Smoothing method (default: SMA) public Adr(ITValuePublisher source, int period, AdrMethod method = AdrMethod.Sma) : this(period, method) { _source = source; source.Pub += Handle; } /// /// Creates ADR from a TBarSeries. /// /// Bar series source /// Period for ADR calculation /// Smoothing method (default: SMA) public Adr(TBarSeries source, int period, AdrMethod method = AdrMethod.Sma) : this(period, method) { var ranges = CalculateRanges(source); _ma.Prime(ranges.Values); Last = _ma.Last; } private void Handle(object? sender, in TValueEventArgs e) => Update(e.Value, e.IsNew); /// /// True if the ADR has warmed up and is providing valid results. /// public override bool IsHot => _ma.IsHot; /// /// Initializes the indicator state using the provided history. /// Note: ADR needs OHLCV data to calculate range properly. /// This Prime method expects pre-calculated range values. /// public override void Prime(ReadOnlySpan source, TimeSpan? step = null) { _ma.Prime(source); Last = _ma.Last; } /// /// Resets the ADR state. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override void Reset() { _ma.Reset(); Last = default; } /// /// Updates ADR with a new bar. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TBar input, bool isNew = true) { double range = input.High - input.Low; // Handle invalid range values if (!double.IsFinite(range) || range < 0) { range = 0; } TValue result = _ma.Update(new TValue(input.Time, range), isNew); Last = result; PubEvent(Last, isNew); return result; } /// /// Updates ADR with a TValue input. /// This treats the input value as the range itself. /// public override TValue Update(TValue input, bool isNew = true) { TValue result = _ma.Update(input, isNew); Last = result; PubEvent(Last, isNew); return result; } /// /// Updates ADR from a TBarSeries. /// public TSeries Update(TBarSeries source) { if (source.Count == 0) { return []; } // Calculate range series TSeries rangeSeries = CalculateRanges(source); // Run MA on ranges var result = _ma.Update(rangeSeries); Last = _ma.Last; return result; } /// /// Updates ADR from a TSeries (assumes values are already ranges). /// public override TSeries Update(TSeries source) { var result = _ma.Update(source); Last = _ma.Last; return result; } /// /// Disposes the ADR and unsubscribes from the source. /// protected override void Dispose(bool disposing) { if (!_disposed) { if (disposing && _source != null) { _source.Pub -= Handle; _source = null; } _disposed = true; } base.Dispose(disposing); } /// /// Calculates High-Low ranges from bar series. /// private static TSeries CalculateRanges(TBarSeries source) { var t = new List(source.Count); var v = new List(source.Count); for (int i = 0; i < source.Count; i++) { var bar = source[i]; double range = bar.High - bar.Low; // Handle invalid values if (!double.IsFinite(range) || range < 0) { range = 0; } t.Add(bar.Time); v.Add(range); } return new TSeries(t, v); } /// /// Calculates ADR for the entire series using a new instance. /// public static TSeries Batch(TBarSeries source, int period, AdrMethod method = AdrMethod.Sma) { var adr = new Adr(period, method); return adr.Update(source); } public static (TSeries Results, Adr Indicator) Calculate(TBarSeries source, int period, AdrMethod method = AdrMethod.Sma) { var indicator = new Adr(period, method); TSeries results = indicator.Update(source); return (results, indicator); } } /// /// Smoothing method for ADR calculation. /// public enum AdrMethod { /// Simple Moving Average Sma = 1, /// Exponential Moving Average Ema = 2, /// Weighted Moving Average Wma = 3 }