/**
* Elder-Ray Index Indicator
* Measures buying and selling pressure using Bull Power and Bear Power
*/
export interface ElderRayData {
// Bull Power: High - EMA
bullPower: number
// Bear Power: Low - EMA
bearPower: number
// Combined power
totalPower: number // Bull Power + Bear Power
// Trend analysis
trend: 'bullish' | 'bearish' | 'neutral'
// Power balance
powerBalance: number // Bull Power / |Bear Power| ratio
// Signal strength
strength: number // Overall signal strength (0-100)
// Divergence detection
bullishDivergence: boolean // Bull Power divergence with price
bearishDivergence: boolean // Bear Power divergence with price
// Current market pressure
pressure: 'buying' | 'selling' | 'balanced'
}
/**
* Calculate Elder-Ray Index
* @param highs Array of high prices
* @param lows Array of low prices
* @param closes Array of close prices
* @param period EMA period (default 13, as recommended by Elder)
* @returns ElderRayData object
*/
export function calculateElderRay(
highs: number[],
lows: number[],
closes: number[],
period: number = 13
): ElderRayData | null {
if (highs.length < period || lows.length < period || closes.length < period) {
return null
}
// Calculate EMA of closes
const ema = calculateEMA(closes, period)
if (!ema) return null
// Get current high, low, and close
const currentHigh = highs[highs.length - 1]
const currentLow = lows[lows.length - 1]
// Calculate Bull Power and Bear Power
const bullPower = currentHigh - ema
const bearPower = currentLow - ema
const totalPower = bullPower + bearPower
// Determine trend based on power balance
let trend: 'bullish' | 'bearish' | 'neutral' = 'neutral'
if (bullPower > Math.abs(bearPower) * 0.7) {
trend = 'bullish'
} else if (Math.abs(bearPower) > bullPower * 0.7) {
trend = 'bearish'
}
// Calculate power balance ratio
const powerBalance = bearPower !== 0 ? bullPower / Math.abs(bearPower) : bullPower > 0 ? 1 : -1
// Calculate signal strength based on power magnitudes
const bullStrength = Math.abs(bullPower) / ema * 100
const bearStrength = Math.abs(bearPower) / ema * 100
const strength = Math.min(100, (bullStrength + bearStrength) / 2)
// Check for divergences (simplified version)
let bullishDivergence = false
let bearishDivergence = false
if (highs.length >= period * 2 && lows.length >= period * 2) {
// Compare recent bull power with previous period
const recentBullPowers = highs.slice(-period).map((h, i) => h - calculateEMA(closes.slice(-period - i, -i || undefined), period)!)
const prevBullPowers = highs.slice(-period * 2, -period).map((h, i) => h - calculateEMA(closes.slice(-period * 2 - i, -period - i), period)!)
const recentAvgBull = recentBullPowers.reduce((sum, p) => sum + p, 0) / recentBullPowers.length
const prevAvgBull = prevBullPowers.reduce((sum, p) => sum + p, 0) / prevBullPowers.length
const recentHigh = Math.max(...highs.slice(-period))
const prevHigh = Math.max(...highs.slice(-period * 2, -period))
// Bullish divergence: lower highs but higher bull power
if (recentHigh < prevHigh && recentAvgBull > prevAvgBull) {
bullishDivergence = true
}
// Similar check for bear power
const recentBearPowers = lows.slice(-period).map((l, i) => l - calculateEMA(closes.slice(-period - i, -i || undefined), period)!)
const prevBearPowers = lows.slice(-period * 2, -period).map((l, i) => l - calculateEMA(closes.slice(-period * 2 - i, -period - i), period)!)
const recentAvgBear = recentBearPowers.reduce((sum, p) => sum + p, 0) / recentBearPowers.length
const prevAvgBear = prevBearPowers.reduce((sum, p) => sum + p, 0) / prevBearPowers.length
const recentLow = Math.min(...lows.slice(-period))
const prevLow = Math.min(...lows.slice(-period * 2, -period))
// Bearish divergence: higher lows but lower bear power (more negative)
if (recentLow > prevLow && recentAvgBear < prevAvgBear) {
bearishDivergence = true
}
}
// Determine current market pressure
let pressure: 'buying' | 'selling' | 'balanced' = 'balanced'
if (bullPower > Math.abs(bearPower)) {
pressure = 'buying'
} else if (Math.abs(bearPower) > bullPower) {
pressure = 'selling'
}
return {
bullPower,
bearPower,
totalPower,
trend,
powerBalance,
strength,
bullishDivergence,
bearishDivergence,
pressure
}
}
/**
* Helper function to calculate EMA
*/
function calculateEMA(prices: number[], period: number): number | null {
if (prices.length < period) return null
const multiplier = 2 / (period + 1)
let ema = prices[0]
for (let i = 1; i < prices.length; i++) {
ema = (prices[i] - ema) * multiplier + ema
}
return ema
}
/**
* Get Elder-Ray trading signal
* @param elderRay ElderRayData object
* @returns Trading signal
*/
export function getElderRaySignal(elderRay: ElderRayData): 'buy' | 'sell' | 'neutral' {
const { trend, bullishDivergence, bearishDivergence, pressure, bullPower, bearPower } = elderRay
// Strong divergence signals
if (bullishDivergence && pressure === 'buying') {
return 'buy'
} else if (bearishDivergence && pressure === 'selling') {
return 'sell'
}
// Trend continuation signals
if (trend === 'bullish' && bullPower > 0 && bearPower > -0.5) {
return 'buy'
} else if (trend === 'bearish' && bearPower < 0 && bullPower < 0.5) {
return 'sell'
}
// Extreme power signals
const powerRatio = Math.abs(bullPower / bearPower)
if (powerRatio > 3 && bullPower > 0) {
return 'buy' // Very strong buying pressure
} else if (powerRatio > 3 && bearPower < 0) {
return 'sell' // Very strong selling pressure
}
return 'neutral'
}
/**
* Calculate Elder-Ray Power Histogram
* @param elderRay ElderRayData object
* @returns Histogram value for visualization
*/
export function getElderRayHistogram(elderRay: ElderRayData): number {
// Return the difference between bull and bear power
return elderRay.bullPower - Math.abs(elderRay.bearPower)
}
/**
* Check Elder-Ray for overbought/oversold conditions
* @param elderRay ElderRayData object
* @param history Array of previous ElderRayData for normalization
* @returns Overbought/oversold status
*/
export function getElderRayOverboughtOversold(
elderRay: ElderRayData,
history: ElderRayData[] = []
): 'overbought' | 'oversold' | 'neutral' {
if (history.length < 20) {
// Not enough history for reliable assessment
return 'neutral'
}
// Calculate z-score of current bull power vs historical bull powers
const bullPowers = history.map(h => h.bullPower)
const bullMean = bullPowers.reduce((sum, p) => sum + p, 0) / bullPowers.length
const bullStd = Math.sqrt(bullPowers.reduce((sum, p) => sum + Math.pow(p - bullMean, 2), 0) / bullPowers.length)
const bullZScore = (elderRay.bullPower - bullMean) / bullStd
// Similar for bear power
const bearPowers = history.map(h => Math.abs(h.bearPower))
const bearMean = bearPowers.reduce((sum, p) => sum + p, 0) / bearPowers.length
const bearStd = Math.sqrt(bearPowers.reduce((sum, p) => sum + Math.pow(p - bearMean, 2), 0) / bearPowers.length)
const bearZScore = (Math.abs(elderRay.bearPower) - bearMean) / bearStd
if (bullZScore > 2) return 'overbought' // Bull power is unusually high
if (bearZScore > 2) return 'oversold' // Bear power is unusually high
return 'neutral'
}