using System.Runtime.CompilerServices; namespace QuanTAlib; /// /// AO: Awesome Oscillator /// /// /// The Awesome Oscillator (AO) is a momentum indicator used to measure market momentum. /// It calculates the difference between a 5-period and 34-period Simple Moving Average (SMA) /// of the median prices (High + Low) / 2. /// /// Calculation: /// Median Price = (High + Low) / 2 /// AO = SMA(Median Price, 5) - SMA(Median Price, 34) /// /// Sources: /// https://www.investopedia.com/terms/a/awesomeoscillator.asp /// https://www.tradingview.com/support/solutions/43000501826-awesome-oscillator-ao/ /// [SkipLocalsInit] public sealed class Ao : ITValuePublisher { private readonly Sma _smaFast; private readonly Sma _smaSlow; /// /// Display name for the indicator. /// public string Name { get; } public event Action? Pub; /// /// Current AO value. /// public TValue Last { get; private set; } /// /// True if the AO has enough data to produce valid results. /// public bool IsHot => _smaSlow.IsHot; /// /// The number of bars required to warm up the indicator. /// public int WarmupPeriod { get; } /// /// Creates AO with specified periods. /// /// Fast SMA period (default 5) /// Slow SMA period (default 34) public Ao(int fastPeriod = 5, int slowPeriod = 34) { 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)); _smaFast = new Sma(fastPeriod); _smaSlow = new Sma(slowPeriod); WarmupPeriod = slowPeriod; Name = $"Ao({fastPeriod},{slowPeriod})"; } /// /// Resets the AO state. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Reset() { _smaFast.Reset(); _smaSlow.Reset(); Last = default; } /// /// Updates the AO with a new bar. /// /// The new bar data /// Whether this is a new bar or an update to the last bar /// The updated AO value [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TBar input, bool isNew = true) { double medianPrice = (input.High + input.Low) * 0.5; var val = new TValue(input.Time, medianPrice); var sFast = _smaFast.Update(val, isNew); var sSlow = _smaSlow.Update(val, isNew); double ao = sFast.Value - sSlow.Value; Last = new TValue(input.Time, ao); Pub?.Invoke(Last); return Last; } /// /// Updates the AO with a new value (assumes value is Median Price). /// /// The new value /// Whether this is a new value or an update to the last value /// The updated AO value [MethodImpl(MethodImplOptions.AggressiveInlining)] public TValue Update(TValue input, bool isNew = true) { var sFast = _smaFast.Update(input, isNew); var sSlow = _smaSlow.Update(input, isNew); double ao = sFast.Value - sSlow.Value; Last = new TValue(input.Time, ao); Pub?.Invoke(Last); return Last; } /// /// Updates the AO with a series of bars. /// /// The source series of bars /// The AO 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 AO for the entire series using a new instance. /// /// Input series /// Fast SMA period (default 5) /// Slow SMA period (default 34) /// AO series public static TSeries Batch(TBarSeries source, int fastPeriod = 5, int slowPeriod = 34) { var ao = new Ao(fastPeriod, slowPeriod); return ao.Update(source); } }