/**
* Rainbow Moving Average Indicator
* Multiple moving averages with different periods for trend visualization
*/
export interface RainbowMAData {
// Individual MA values (typically 8-10 MAs)
ma2: number // Fastest MA (violet)
ma3: number // Red
ma4: number // Orange
ma5: number // Yellow
ma6: number // Green
ma7: number // Blue
ma8: number // Indigo
ma9: number // Violet (slowest)
// Trend analysis
trend: 'strong_bullish' | 'bullish' | 'neutral' | 'bearish' | 'strong_bearish'
// Rainbow alignment (all MAs in order)
alignment: 'bullish_alignment' | 'bearish_alignment' | 'mixed'
// Current price position vs rainbow
position: 'above_rainbow' | 'in_rainbow' | 'below_rainbow'
// Trend strength (0-100)
strength: number
// Fast vs slow MA spread
spread: number // Difference between fastest and slowest MA (%)
compression: boolean // Whether the rainbow is compressing (potential breakout)
}
/**
* Calculate Rainbow Moving Average
* @param prices Array of closing prices
* @param periods Array of periods for each MA (default rainbow periods)
* @returns RainbowMAData object
*/
export function calculateRainbowMA(
prices: number[],
periods: number[] = [2, 3, 4, 5, 6, 7, 8, 9]
): RainbowMAData | null {
if (periods.length < 8 || prices.length < Math.max(...periods)) {
return null
}
// Calculate all MAs
const mas: number[] = []
for (const period of periods) {
const ma = prices.slice(-period).reduce((sum, price) => sum + price, 0) / period
mas.push(ma)
}
// Extract individual MA values
const [ma2, ma3, ma4, ma5, ma6, ma7, ma8, ma9] = mas
// Determine alignment (bullish = fast MAs above slow MAs)
let bullishCount = 0
let bearishCount = 0
for (let i = 0; i < mas.length - 1; i++) {
if (mas[i] > mas[i + 1]) {
bullishCount++
} else if (mas[i] < mas[i + 1]) {
bearishCount++
}
}
let alignment: 'bullish_alignment' | 'bearish_alignment' | 'mixed'
if (bullishCount >= mas.length - 2) {
alignment = 'bullish_alignment'
} else if (bearishCount >= mas.length - 2) {
alignment = 'bearish_alignment'
} else {
alignment = 'mixed'
}
// Determine trend based on alignment and price position
let trend: 'strong_bullish' | 'bullish' | 'neutral' | 'bearish' | 'strong_bearish' = 'neutral'
if (alignment === 'bullish_alignment') {
trend = 'bullish'
// Check for strong bullish (price well above rainbow)
const currentPrice = prices[prices.length - 1]
if (currentPrice > ma2 * 1.02) {
trend = 'strong_bullish'
}
} else if (alignment === 'bearish_alignment') {
trend = 'bearish'
// Check for strong bearish (price well below rainbow)
const currentPrice = prices[prices.length - 1]
if (currentPrice < ma9 * 0.98) {
trend = 'strong_bearish'
}
}
// Current price position vs rainbow
const currentPrice = prices[prices.length - 1]
const highestMA = Math.max(...mas)
const lowestMA = Math.min(...mas)
let position: 'above_rainbow' | 'in_rainbow' | 'below_rainbow'
if (currentPrice > highestMA) {
position = 'above_rainbow'
} else if (currentPrice < lowestMA) {
position = 'below_rainbow'
} else {
position = 'in_rainbow'
}
// Calculate trend strength
let strength = 50 // Base neutral strength
if (alignment === 'bullish_alignment') {
strength += bullishCount * 5
if (position === 'above_rainbow') strength += 20
} else if (alignment === 'bearish_alignment') {
strength -= bearishCount * 5
if (position === 'below_rainbow') strength -= 20
}
strength = Math.max(0, Math.min(100, strength))
// Calculate spread between fastest and slowest MA
const spread = ((ma2 - ma9) / ma9) * 100
// Check for compression (all MAs close together)
const avgMA = mas.reduce((sum, ma) => sum + ma, 0) / mas.length
const variance = mas.reduce((sum, ma) => sum + Math.pow(ma - avgMA, 2), 0) / mas.length
const stdDev = Math.sqrt(variance)
const compression = stdDev / avgMA < 0.01 // Less than 1% standard deviation
return {
ma2, ma3, ma4, ma5, ma6, ma7, ma8, ma9,
trend,
alignment,
position,
strength,
spread,
compression
}
}
/**
* Get Rainbow MA signal
* @param rainbow RainbowMAData object
* @returns Trading signal
*/
export function getRainbowMASignal(rainbow: RainbowMAData): 'buy' | 'sell' | 'neutral' {
const { trend, alignment, position, compression } = rainbow
// Compression followed by alignment change is a strong signal
if (compression && alignment === 'bullish_alignment') {
return 'buy'
} else if (compression && alignment === 'bearish_alignment') {
return 'sell'
}
// Strong trend signals
if (trend === 'strong_bullish' && position === 'above_rainbow') {
return 'buy'
} else if (trend === 'strong_bearish' && position === 'below_rainbow') {
return 'sell'
}
// Moderate trend signals
if (trend === 'bullish' && position === 'above_rainbow') {
return 'buy'
} else if (trend === 'bearish' && position === 'below_rainbow') {
return 'sell'
}
return 'neutral'
}
/**
* Calculate Rainbow MA slope
* @param rainbow RainbowMAData object
* @returns Average slope of all MAs
*/
export function getRainbowMASlope(rainbow: RainbowMAData): number {
const mas = [rainbow.ma2, rainbow.ma3, rainbow.ma4, rainbow.ma5,
rainbow.ma6, rainbow.ma7, rainbow.ma8, rainbow.ma9]
// Calculate slope using linear regression
let sumX = 0
let sumY = 0
let sumXY = 0
let sumXX = 0
for (let i = 0; i < mas.length; i++) {
sumX += i
sumY += mas[i]
sumXY += i * mas[i]
sumXX += i * i
}
const n = mas.length
const slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX)
return slope
}
/**
* Check for Rainbow MA fan formation
* @param rainbow RainbowMAData object
* @returns Whether MAs are forming a fan pattern
*/
export function isRainbowMAFan(rainbow: RainbowMAData): boolean {
const mas = [rainbow.ma2, rainbow.ma3, rainbow.ma4, rainbow.ma5,
rainbow.ma6, rainbow.ma7, rainbow.ma8, rainbow.ma9]
// Check if MAs are roughly parallel (fan formation)
let parallelCount = 0
const tolerance = 0.005 // 0.5% tolerance
for (let i = 0; i < mas.length - 2; i++) {
const slope1 = mas[i + 1] - mas[i]
const slope2 = mas[i + 2] - mas[i + 1]
if (Math.abs(slope1 - slope2) / Math.abs(slope1) < tolerance) {
parallelCount++
}
}
return parallelCount >= mas.length - 3 // Most MAs are parallel
}