using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// ADOSC: Accumulation/Distribution Oscillator (Chaikin Oscillator) /// /// /// The Chaikin Oscillator is a momentum indicator for the Accumulation/Distribution Line (ADL). /// It calculates the difference between two Exponential Moving Averages (EMAs) of the ADL. /// /// Calculation: /// ADOSC = EMA(Fast, ADL) - EMA(Slow, ADL) /// /// Standard Parameters: /// Fast Period: 3 /// Slow Period: 10 /// /// Sources: /// https://www.investopedia.com/terms/c/chaikinoscillator.asp /// https://school.stockcharts.com/doku.php?id=technical_indicators:chaikin_oscillator /// [SkipLocalsInit] public sealed class Adosc : ITValuePublisher { private readonly Adl _adl; private readonly Ema _emaFast; private readonly Ema _emaSlow; /// /// Display name for the indicator. /// public string Name { get; } public event Action? Pub; /// /// Current ADOSC value. /// public TValue Last { get; private set; } /// /// True if the indicator has enough data to produce valid results. /// public bool IsHot => _emaSlow.IsHot; /// /// The number of bars required to warm up the indicator. /// public int WarmupPeriod { get; } /// /// Creates ADOSC with specified periods. /// /// Fast EMA period (default 3) /// Slow EMA period (default 10) public Adosc(int fastPeriod = 3, int slowPeriod = 10) { if (fastPeriod <= 0) throw new ArgumentException("Fast period must be greater than 0", nameof(fastPeriod)); if (slowPeriod <= 0) throw new ArgumentException("Slow period must be greater than 0", nameof(slowPeriod)); if (fastPeriod >= slowPeriod) throw new ArgumentException("Fast period must be less than slow period", nameof(fastPeriod)); _adl = new Adl(); _emaFast = new Ema(fastPeriod); _emaSlow = new Ema(slowPeriod); WarmupPeriod = slowPeriod; Name = $"Adosc({fastPeriod},{slowPeriod})"; } /// /// Resets the indicator state. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { _adl.Reset(); _emaFast.Reset(); _emaSlow.Reset(); Last = default; } /// /// Updates the indicator with a new ADL value. /// /// The new ADL value /// Whether this is a new value or an update to the last value /// The updated ADOSC value [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TValue input, bool isNew = true) { var eFast = _emaFast.Update(input, isNew); var eSlow = _emaSlow.Update(input, isNew); double adosc = eFast.Value - eSlow.Value; Last = new TValue(input.Time, adosc); Pub?.Invoke(Last); return Last; } /// /// Updates the indicator with a new bar. /// /// The new bar data /// Whether this is a new bar or an update to the last bar /// The updated ADOSC value [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TBar input, bool isNew = true) { var adl = _adl.Update(input, isNew); return Update(adl, isNew); } /// /// Updates the indicator with a series of bars. /// /// The source series of bars /// The ADOSC series public TSeries Update(TBarSeries source) { var t = new List(source.Count); var v = new List(source.Count); Reset(); for (int i = 0; i < source.Count; i++) { var val = Update(source[i], true); t.Add(val.Time); v.Add(val.Value); } return new TSeries(t, v); } /// /// Calculates ADOSC for the entire series using a new instance. /// /// Input series /// Fast EMA period (default 3) /// Slow EMA period (default 10) /// ADOSC series public static TSeries Batch(TBarSeries source, int fastPeriod = 3, int slowPeriod = 10) { var adosc = new Adosc(fastPeriod, slowPeriod); return adosc.Update(source); } /// /// Calculates ADOSC for the entire span. /// /// High prices /// Low prices /// Close prices /// Volume /// Output span /// Fast EMA period (default 3) /// Slow EMA period (default 10) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Calculate(ReadOnlySpan high, ReadOnlySpan low, ReadOnlySpan close, ReadOnlySpan volume, Span output, int fastPeriod = 3, int slowPeriod = 10) { if (high.Length != output.Length) throw new ArgumentException("Source and output spans must be of the same length."); Span adl = high.Length <= 1024 ? stackalloc double[high.Length] : new double[high.Length]; Adl.Calculate(high, low, close, volume, adl); Span fastEma = high.Length <= 1024 ? stackalloc double[high.Length] : new double[high.Length]; Span slowEma = high.Length <= 1024 ? stackalloc double[high.Length] : new double[high.Length]; Ema.Batch(adl, fastEma, fastPeriod); Ema.Batch(adl, slowEma, slowPeriod); SimdExtensions.Subtract(fastEma, slowEma, output); } }