Files
QuanTAlib/lib/volume/adosc/Adosc.cs
T

179 lines
6.1 KiB
C#

using System.Runtime.CompilerServices;
namespace QuanTAlib;
/// <summary>
/// ADOSC: Accumulation/Distribution Oscillator (Chaikin Oscillator)
/// </summary>
/// <remarks>
/// 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
/// </remarks>
[SkipLocalsInit]
public sealed class Adosc : ITValuePublisher
{
private readonly Adl _adl;
private readonly Ema _emaFast;
private readonly Ema _emaSlow;
/// <summary>
/// Display name for the indicator.
/// </summary>
public string Name { get; }
public event Action<TValue>? Pub;
/// <summary>
/// Current ADOSC value.
/// </summary>
public TValue Last { get; private set; }
/// <summary>
/// True if the indicator has enough data to produce valid results.
/// </summary>
public bool IsHot => _emaSlow.IsHot;
/// <summary>
/// The number of bars required to warm up the indicator.
/// </summary>
public int WarmupPeriod { get; }
/// <summary>
/// Creates ADOSC with specified periods.
/// </summary>
/// <param name="fastPeriod">Fast EMA period (default 3)</param>
/// <param name="slowPeriod">Slow EMA period (default 10)</param>
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})";
}
/// <summary>
/// Resets the indicator state.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
_adl.Reset();
_emaFast.Reset();
_emaSlow.Reset();
Last = default;
}
/// <summary>
/// Updates the indicator with a new ADL value.
/// </summary>
/// <param name="input">The new ADL value</param>
/// <param name="isNew">Whether this is a new value or an update to the last value</param>
/// <returns>The updated ADOSC value</returns>
[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;
}
/// <summary>
/// Updates the indicator with a new bar.
/// </summary>
/// <param name="input">The new bar data</param>
/// <param name="isNew">Whether this is a new bar or an update to the last bar</param>
/// <returns>The updated ADOSC value</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(TBar input, bool isNew = true)
{
var adl = _adl.Update(input, isNew);
return Update(adl, isNew);
}
/// <summary>
/// Updates the indicator with a series of bars.
/// </summary>
/// <param name="source">The source series of bars</param>
/// <returns>The ADOSC series</returns>
public TSeries Update(TBarSeries source)
{
var t = new List<long>(source.Count);
var v = new List<double>(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);
}
/// <summary>
/// Calculates ADOSC for the entire series using a new instance.
/// </summary>
/// <param name="source">Input series</param>
/// <param name="fastPeriod">Fast EMA period (default 3)</param>
/// <param name="slowPeriod">Slow EMA period (default 10)</param>
/// <returns>ADOSC series</returns>
public static TSeries Batch(TBarSeries source, int fastPeriod = 3, int slowPeriod = 10)
{
var adosc = new Adosc(fastPeriod, slowPeriod);
return adosc.Update(source);
}
/// <summary>
/// Calculates ADOSC for the entire span.
/// </summary>
/// <param name="high">High prices</param>
/// <param name="low">Low prices</param>
/// <param name="close">Close prices</param>
/// <param name="volume">Volume</param>
/// <param name="output">Output span</param>
/// <param name="fastPeriod">Fast EMA period (default 3)</param>
/// <param name="slowPeriod">Slow EMA period (default 10)</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Calculate(ReadOnlySpan<double> high, ReadOnlySpan<double> low, ReadOnlySpan<double> close, ReadOnlySpan<double> volume, Span<double> 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<double> adl = high.Length <= 1024 ? stackalloc double[high.Length] : new double[high.Length];
Adl.Calculate(high, low, close, volume, adl);
Span<double> fastEma = high.Length <= 1024 ? stackalloc double[high.Length] : new double[high.Length];
Span<double> 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);
}
}