/**
* Triple Exponential Moving Average (TEMA) Indicator
* Moving average designed to further reduce lag compared to DEMA
*/
import { calculateEMA } from './moving-averages'
export function calculateTEMA(closes: number[], period: number = 20): number[] {
if (closes.length < 4) return []
// Use adaptive period if data is insufficient
const effectivePeriod = Math.min(period, Math.max(2, Math.floor(closes.length / 3)))
const tema: number[] = []
// Calculate EMA(n)
const ema1 = calculateEMA(closes, effectivePeriod)
if (ema1.length === 0) {
// Fallback: return last close
return [closes[closes.length - 1]]
}
// Calculate EMA(EMA(n))
const ema2 = calculateEMA(ema1, Math.min(effectivePeriod, ema1.length))
if (ema2.length === 0) {
// Fallback: return last EMA value
return [ema1[ema1.length - 1]]
}
// Calculate EMA(EMA(EMA(n)))
const ema3 = calculateEMA(ema2, Math.min(effectivePeriod, ema2.length))
if (ema3.length === 0) {
// Fallback: use DEMA calculation
const demaValue = 2 * ema1[ema1.length - 1] - ema2[ema2.length - 1]
return [demaValue]
}
// TEMA = 3 × EMA(n) - 3 × EMA(EMA(n)) + EMA(EMA(EMA(n)))
const offset1 = ema1.length - ema3.length
const offset2 = ema2.length - ema3.length
for (let i = 0; i < ema3.length; i++) {
const ema1Idx = i + offset1
const ema2Idx = i + offset2
if (ema1Idx >= 0 && ema1Idx < ema1.length && ema2Idx >= 0 && ema2Idx < ema2.length) {
const temaValue = 3 * ema1[ema1Idx] - 3 * ema2[ema2Idx] + ema3[i]
tema.push(temaValue)
}
}
return tema.length > 0 ? tema : [closes[closes.length - 1]]
}