UNPKG

@baguskto/saham

Version:

MCP Server untuk data saham Indonesia (IDX) - Implementasi Node.js/TypeScript

357 lines 13.1 kB
"use strict"; /** * Technical Analysis utilities for stock data */ Object.defineProperty(exports, "__esModule", { value: true }); exports.TechnicalAnalysis = void 0; class TechnicalAnalysis { /** * Calculate Simple Moving Average */ static calculateSMA(data, period) { const sma = []; for (let i = 0; i < data.length; i++) { if (i < period - 1) { sma.push(NaN); } else { const sum = data.slice(i - period + 1, i + 1) .reduce((acc, point) => acc + point.close, 0); sma.push(sum / period); } } return sma; } /** * Calculate Exponential Moving Average */ static calculateEMA(data, period) { const ema = []; const multiplier = 2 / (period + 1); // Start with SMA for the first EMA value let sum = 0; for (let i = 0; i < period; i++) { if (i < data.length) { sum += data[i].close; if (i < period - 1) { ema.push(NaN); } else { ema.push(sum / period); } } } // Calculate EMA for remaining values for (let i = period; i < data.length; i++) { const todayEMA = (data[i].close - ema[i - 1]) * multiplier + ema[i - 1]; ema.push(todayEMA); } return ema; } /** * Calculate Relative Strength Index */ static calculateRSI(data, period = 14) { const rsi = []; const gains = []; const losses = []; // Calculate initial gains and losses for (let i = 1; i < data.length; i++) { const change = data[i].close - data[i - 1].close; gains.push(change > 0 ? change : 0); losses.push(change < 0 ? Math.abs(change) : 0); } // Calculate RSI for (let i = 0; i < gains.length; i++) { if (i < period - 1) { rsi.push(NaN); } else { const avgGain = gains.slice(i - period + 1, i + 1) .reduce((sum, gain) => sum + gain, 0) / period; const avgLoss = losses.slice(i - period + 1, i + 1) .reduce((sum, loss) => sum + loss, 0) / period; if (avgLoss === 0) { rsi.push(100); } else { const rs = avgGain / avgLoss; rsi.push(100 - (100 / (1 + rs))); } } } // Add NaN for the first data point (no change available) return [NaN, ...rsi]; } /** * Calculate MACD (Moving Average Convergence Divergence) */ static calculateMACD(data, fastPeriod = 12, slowPeriod = 26, signalPeriod = 9) { const fastEMA = this.calculateEMA(data, fastPeriod); const slowEMA = this.calculateEMA(data, slowPeriod); // Calculate MACD line const macd = []; for (let i = 0; i < data.length; i++) { if (isNaN(fastEMA[i]) || isNaN(slowEMA[i])) { macd.push(NaN); } else { macd.push(fastEMA[i] - slowEMA[i]); } } // Calculate signal line (EMA of MACD) const signal = this.calculateEMAFromValues(macd, signalPeriod); // Calculate histogram const histogram = []; for (let i = 0; i < macd.length; i++) { if (isNaN(macd[i]) || isNaN(signal[i])) { histogram.push(NaN); } else { histogram.push(macd[i] - signal[i]); } } return { macd, signal, histogram }; } /** * Calculate Bollinger Bands */ static calculateBollingerBands(data, period = 20, stdDev = 2) { const sma = this.calculateSMA(data, period); const upper = []; const middle = []; const lower = []; for (let i = 0; i < data.length; i++) { if (i < period - 1) { upper.push(NaN); middle.push(NaN); lower.push(NaN); } else { const slice = data.slice(i - period + 1, i + 1); const mean = sma[i]; const variance = slice.reduce((sum, point) => sum + Math.pow(point.close - mean, 2), 0) / period; const standardDeviation = Math.sqrt(variance); middle.push(mean); upper.push(mean + (standardDeviation * stdDev)); lower.push(mean - (standardDeviation * stdDev)); } } return { upper, middle, lower }; } /** * Calculate support and resistance levels */ static calculateSupportResistance(data) { const recentData = data.slice(-50); // Last 50 data points const highs = recentData.map(d => d.high); const lows = recentData.map(d => d.low); // Simple approach: use recent highest high and lowest low const resistance = Math.max(...highs); const support = Math.min(...lows); return { support, resistance }; } /** * Calculate volatility (annualized) */ static calculateVolatility(data, period = 30) { if (data.length < 2) return 0; const recentData = data.slice(-period); const returns = []; // Calculate daily returns for (let i = 1; i < recentData.length; i++) { const returnRate = (recentData[i].close - recentData[i - 1].close) / recentData[i - 1].close; returns.push(returnRate); } if (returns.length === 0) return 0; // Calculate standard deviation of returns const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length; const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length; const dailyVolatility = Math.sqrt(variance); // Annualize (assuming 252 trading days per year) return dailyVolatility * Math.sqrt(252); } /** * Perform comprehensive stock analysis */ static analyzeStock(data, ticker, period) { if (data.length < 2) { throw new Error('Insufficient data for analysis'); } const currentPoint = data[data.length - 1]; const previousPoint = data[data.length - 2]; // Price analysis const priceChange = currentPoint.close - previousPoint.close; const priceChangePercent = (priceChange / previousPoint.close) * 100; const highs = data.map(d => d.high); const lows = data.map(d => d.low); const volumes = data.map(d => d.volume).filter(v => v > 0); const high52Week = Math.max(...highs); const low52Week = Math.min(...lows); const averageVolume = volumes.length > 0 ? volumes.reduce((sum, vol) => sum + vol, 0) / volumes.length : 0; // Technical indicators const sma = { 20: this.calculateSMA(data, 20), 50: this.calculateSMA(data, 50), 200: this.calculateSMA(data, 200) }; const ema = { 12: this.calculateEMA(data, 12), 26: this.calculateEMA(data, 26) }; const rsi = this.calculateRSI(data); const macd = this.calculateMACD(data); const bollinger = this.calculateBollingerBands(data); const { support, resistance } = this.calculateSupportResistance(data); const volatility = this.calculateVolatility(data); // Generate summary const summary = this.generateSummary(data, { sma, ema, rsi, macd, bollinger }); return { ticker, period, dataPoints: data.length, priceAnalysis: { currentPrice: currentPoint.close, priceChange, priceChangePercent, high52Week, low52Week, averageVolume }, technicalIndicators: { sma, ema, rsi, macd, bollinger, support, resistance, volatility }, summary }; } /** * Generate trading summary and recommendation */ static generateSummary(data, indicators) { const currentPrice = data[data.length - 1].close; const signals = []; // SMA trend analysis const sma20 = indicators.sma[20]; const sma50 = indicators.sma[50]; const currentSMA20 = sma20[sma20.length - 1]; const currentSMA50 = sma50[sma50.length - 1]; if (!isNaN(currentSMA20) && !isNaN(currentSMA50)) { if (currentPrice > currentSMA20 && currentSMA20 > currentSMA50) { signals.push({ signal: 'bullish', weight: 0.3 }); } else if (currentPrice < currentSMA20 && currentSMA20 < currentSMA50) { signals.push({ signal: 'bearish', weight: 0.3 }); } else { signals.push({ signal: 'neutral', weight: 0.1 }); } } // RSI analysis const rsi = indicators.rsi; const currentRSI = rsi[rsi.length - 1]; if (!isNaN(currentRSI)) { if (currentRSI > 70) { signals.push({ signal: 'bearish', weight: 0.25 }); // Overbought } else if (currentRSI < 30) { signals.push({ signal: 'bullish', weight: 0.25 }); // Oversold } else { signals.push({ signal: 'neutral', weight: 0.1 }); } } // MACD analysis const macd = indicators.macd; const currentMACD = macd.macd[macd.macd.length - 1]; const currentSignal = macd.signal[macd.signal.length - 1]; if (!isNaN(currentMACD) && !isNaN(currentSignal)) { if (currentMACD > currentSignal) { signals.push({ signal: 'bullish', weight: 0.2 }); } else { signals.push({ signal: 'bearish', weight: 0.2 }); } } // Calculate overall sentiment let bullishScore = 0; let bearishScore = 0; let totalWeight = 0; signals.forEach(({ signal, weight }) => { totalWeight += weight; if (signal === 'bullish') bullishScore += weight; else if (signal === 'bearish') bearishScore += weight; }); const bullishPercent = bullishScore / totalWeight; const bearishPercent = bearishScore / totalWeight; let trend; let strength; let recommendation; let confidence; if (bullishPercent > 0.6) { trend = 'bullish'; strength = bullishPercent > 0.8 ? 'strong' : 'moderate'; recommendation = 'buy'; confidence = bullishPercent; } else if (bearishPercent > 0.6) { trend = 'bearish'; strength = bearishPercent > 0.8 ? 'strong' : 'moderate'; recommendation = 'sell'; confidence = bearishPercent; } else { trend = 'neutral'; strength = 'weak'; recommendation = 'hold'; confidence = 0.5; } return { trend, strength, recommendation, confidence }; } /** * Helper function to calculate EMA from array of values */ static calculateEMAFromValues(values, period) { const ema = []; const multiplier = 2 / (period + 1); // Find first non-NaN value for starting point let startIndex = values.findIndex(val => !isNaN(val)); if (startIndex === -1) return values.map(() => NaN); // Fill initial NaN values for (let i = 0; i < startIndex + period - 1; i++) { ema.push(NaN); } // Calculate initial SMA if (startIndex + period - 1 < values.length) { const validValues = values.slice(startIndex, startIndex + period) .filter(val => !isNaN(val)); const initialSMA = validValues.reduce((sum, val) => sum + val, 0) / validValues.length; ema.push(initialSMA); // Calculate EMA for remaining values for (let i = startIndex + period; i < values.length; i++) { if (!isNaN(values[i])) { const todayEMA = (values[i] - ema[i - 1]) * multiplier + ema[i - 1]; ema.push(todayEMA); } else { ema.push(ema[i - 1]); // Carry forward previous EMA if current value is NaN } } } return ema; } } exports.TechnicalAnalysis = TechnicalAnalysis; //# sourceMappingURL=technical-analysis.js.map