/**
* Volume Weighted Moving Average (VWMA) Indicator
* Moving average weighted by trading volume for better price representation
*/
export interface VWMAData {
// VWMA value
vwma: number
// Current price vs VWMA
priceVsVwma: number // Price vs VWMA (%)
position: 'above' | 'below' | 'equal'
// Trend analysis
trend: 'bullish' | 'bearish' | 'neutral'
// Volume analysis
avgVolume: number // Average volume over the period
volumeEfficiency: number // Volume efficiency ratio
// Signal strength
strength: number // Trend strength (0-100)
}
/**
* Calculate Volume Weighted Moving Average
* @param prices Array of closing prices
* @param volumes Array of volume data
* @param period Period for VWMA calculation (default 20)
* @returns VWMAData object
*/
export function calculateVWMA(
prices: number[],
volumes: number[],
period: number = 20
): VWMAData | null {
if (prices.length < period || volumes.length < period) {
return null
}
// Use the most recent 'period' data points
const recentPrices = prices.slice(-period)
const recentVolumes = volumes.slice(-period)
// Calculate VWMA: Σ(Price × Volume) / Σ(Volume)
let priceVolumeSum = 0
let volumeSum = 0
for (let i = 0; i < period; i++) {
priceVolumeSum += recentPrices[i] * recentVolumes[i]
volumeSum += recentVolumes[i]
}
const vwma = volumeSum > 0 ? priceVolumeSum / volumeSum : 0
// Calculate average volume
const avgVolume = volumeSum / period
// Get current price
const currentPrice = prices[prices.length - 1]
const currentVolume = volumes[volumes.length - 1]
// Price vs VWMA
const priceVsVwma = ((currentPrice - vwma) / vwma) * 100
let position: 'above' | 'below' | 'equal' = 'equal'
if (currentPrice > vwma * 1.0001) {
position = 'above'
} else if (currentPrice < vwma * 0.9999) {
position = 'below'
}
// Determine trend based on VWMA slope (compare with previous VWMA)
let trend: 'bullish' | 'bearish' | 'neutral' = 'neutral'
if (prices.length >= period + 1 && volumes.length >= period + 1) {
const prevPrices = prices.slice(-period - 1, -1)
const prevVolumes = volumes.slice(-period - 1, -1)
let prevPriceVolumeSum = 0
let prevVolumeSum = 0
for (let i = 0; i < period; i++) {
prevPriceVolumeSum += prevPrices[i] * prevVolumes[i]
prevVolumeSum += prevVolumes[i]
}
const prevVwma = prevVolumeSum > 0 ? prevPriceVolumeSum / prevVolumeSum : 0
if (vwma > prevVwma * 1.0001) {
trend = 'bullish'
} else if (vwma < prevVwma * 0.9999) {
trend = 'bearish'
}
}
// Calculate volume efficiency (ratio of current volume to average)
const volumeEfficiency = currentVolume / avgVolume
// Calculate trend strength based on volume confirmation
let strength = 0
if (trend !== 'neutral') {
// Base strength on VWMA slope and volume confirmation
const slopeStrength = Math.abs(priceVsVwma) * 2
const volumeStrength = Math.min(volumeEfficiency, 2) * 25 // Max 50 from volume
strength = Math.min(100, slopeStrength + volumeStrength)
}
return {
vwma,
priceVsVwma,
position,
trend,
avgVolume,
volumeEfficiency,
strength
}
}
/**
* Calculate multiple VWMAs for different periods
* @param prices Array of closing prices
* @param volumes Array of volume data
* @param periods Array of periods to calculate VWMA for
* @returns Array of VWMAData objects
*/
export function calculateMultipleVWMA(
prices: number[],
volumes: number[],
periods: number[] = [10, 20, 50]
): VWMAData[] {
return periods
.map(period => calculateVWMA(prices, volumes, period))
.filter((vwma): vwma is VWMAData => vwma !== null)
}
/**
* Get VWMA crossover signal
* @param fastVwma Fast VWMA data
* @param slowVwma Slow VWMA data
* @returns Crossover signal
*/
export function getVWMACrossoverSignal(
fastVwma: VWMAData,
slowVwma: VWMAData
): 'bullish_crossover' | 'bearish_crossover' | 'neutral' {
// Check if fast VWMA crosses above slow VWMA (bullish)
// or below slow VWMA (bearish)
const fastAboveSlow = fastVwma.vwma > slowVwma.vwma
const priceAboveFast = fastVwma.position === 'above'
if (fastAboveSlow && priceAboveFast) {
return 'bullish_crossover'
} else if (!fastAboveSlow && fastVwma.position === 'below') {
return 'bearish_crossover'
}
return 'neutral'
}