/**
* Chaikin Oscillator Indicator
* MACD of Accumulation/Distribution Line
*/
import { calculateEMA } from './moving-averages'
export interface ChaikinOscillatorData {
chaikinOsc: number | null
fastEMA: number | null
slowEMA: number | null
signal: 'bullish' | 'bearish' | 'neutral' | null
}
export function calculateChaikinOscillator(
highs: number[],
lows: number[],
closes: number[],
volumes: number[],
fastPeriod: number = 3,
slowPeriod: number = 10
): ChaikinOscillatorData {
// Minimum 5 data points required
if (highs.length < 5 || lows.length < 5 || closes.length < 5 || volumes.length < 5) {
return {
chaikinOsc: null,
fastEMA: null,
slowEMA: null,
signal: null,
}
}
// Use adaptive periods
const effectiveSlowPeriod = Math.min(slowPeriod, Math.floor(highs.length * 0.7))
const effectiveFastPeriod = Math.min(fastPeriod, Math.floor(effectiveSlowPeriod / 3))
// Calculate Accumulation/Distribution Line first
const adl: number[] = []
let adlValue = 0
for (let i = 0; i < closes.length; i++) {
const high = highs[i]
const low = lows[i]
const close = closes[i]
const volume = volumes[i] || 0
// Avoid division by zero
if (high === low) {
adl.push(adlValue) // No change if no range
} else {
// Money Flow Multiplier = ((Close - Low) - (High - Close)) / (High - Low)
const moneyFlowMultiplier = ((close - low) - (high - close)) / (high - low)
const moneyFlowVolume = moneyFlowMultiplier * volume
adlValue += moneyFlowVolume
adl.push(adlValue)
}
}
// Calculate EMAs of ADL using effective periods
const fastEMA_ADL = calculateEMA(adl, Math.max(2, effectiveFastPeriod))
const slowEMA_ADL = calculateEMA(adl, Math.max(3, effectiveSlowPeriod))
if (fastEMA_ADL.length === 0 || slowEMA_ADL.length === 0) {
return {
chaikinOsc: null,
fastEMA: null,
slowEMA: null,
signal: null,
}
}
// Chaikin Oscillator = Fast EMA of ADL - Slow EMA of ADL
const currentFastEMA = fastEMA_ADL[fastEMA_ADL.length - 1]
const currentSlowEMA = slowEMA_ADL[slowEMA_ADL.length - 1]
const chaikinOsc = currentFastEMA - currentSlowEMA
// Determine signal
let signal: 'bullish' | 'bearish' | 'neutral' | null = null
if (chaikinOsc > 0) signal = 'bullish'
else if (chaikinOsc < 0) signal = 'bearish'
else signal = 'neutral'
return {
chaikinOsc,
fastEMA: currentFastEMA,
slowEMA: currentSlowEMA,
signal,
}
}