@qubaomingg/stock-mcp

by qubaomingg
Verified
  • src
import axios from 'axios'; import dotenv from 'dotenv'; dotenv.config(); const API_KEY = process.env.ALPHA_VANTAGE_API_KEY; const BASE_URL = 'https://www.alphavantage.co/query'; if (!API_KEY) { console.error('Alpha Vantage API key not found. Please set ALPHA_VANTAGE_API_KEY in your .env file.'); process.exit(1); } /** * Fetches stock data from Alpha Vantage API * @param symbol Stock symbol (e.g., IBM, AAPL) * @param interval Time interval between data points or 'daily' for daily data * @param outputsize Amount of data to return (compact or full) * @returns Formatted stock data as a string */ export async function getStockData(symbol: string | string[], interval: string | string[] | 'daily', outputsize: string = 'compact'): Promise<string> { try { // Ensure parameters are strings, not arrays const symbolStr = Array.isArray(symbol) ? symbol[0] : symbol; const intervalStr = Array.isArray(interval) ? interval[0] : interval; const outputsizeStr = Array.isArray(outputsize) ? outputsize[0] : outputsize; let url: string; let timeSeriesKey: string; if (intervalStr === 'daily') { // Use TIME_SERIES_DAILY endpoint url = `${BASE_URL}?function=TIME_SERIES_DAILY&symbol=${symbolStr}&outputsize=${outputsizeStr}&apikey=${API_KEY}`; timeSeriesKey = 'Time Series (Daily)'; } else { // Use TIME_SERIES_INTRADAY endpoint url = `${BASE_URL}?function=TIME_SERIES_INTRADAY&symbol=${symbolStr}&interval=${intervalStr}&outputsize=${outputsizeStr}&apikey=${API_KEY}`; timeSeriesKey = `Time Series (${intervalStr})`; } const response = await axios.get(url); // Check for error messages from Alpha Vantage if (response.data['Error Message']) { throw new Error(response.data['Error Message']); } if (response.data['Note']) { console.warn('API Usage Note:', response.data['Note']); } // Extract the time series data const timeSeries = response.data[timeSeriesKey]; if (!timeSeries) { throw new Error('No time series data found in the response'); } // Format the data const formattedData = formatTimeSeriesData(timeSeries, symbolStr, intervalStr); return formattedData; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`API request failed: ${error.message}`); } throw error; } } /** * Formats time series data into a readable string */ function formatTimeSeriesData(timeSeries: any, symbol: string, interval: string | 'daily'): string { const dates = Object.keys(timeSeries).sort().reverse(); // Most recent first let result = `Stock data for ${symbol.toUpperCase()} (${interval === 'daily' ? 'Daily' : interval} intervals):\n\n`; // Limit to 10 data points to avoid overwhelming responses const limitedDates = dates.slice(0, 10); for (const date of limitedDates) { const data = timeSeries[date]; result += `${date}:\n`; result += ` Open: ${data['1. open']}\n`; result += ` High: ${data['2. high']}\n`; result += ` Low: ${data['3. low']}\n`; result += ` Close: ${data['4. close']}\n`; result += ` Volume: ${data['5. volume']}\n\n`; } if (dates.length > 10) { result += `... and ${dates.length - 10} more data points available.\n`; } return result; } /** * Analyzes stock data to generate alerts based on price movements * @param symbol Stock symbol (e.g., IBM, AAPL) * @param threshold Percentage threshold for price movement alerts * @returns Formatted alerts as a string */ export async function getStockAlerts(symbol: string | string[], threshold: number = 5): Promise<string> { try { // Ensure symbol is a string, not an array const symbolStr = Array.isArray(symbol) ? symbol[0] : symbol; // Get daily stock data for analysis const url = `${BASE_URL}?function=TIME_SERIES_DAILY&symbol=${symbolStr}&outputsize=compact&apikey=${API_KEY}`; const response = await axios.get(url); if (response.data['Error Message']) { throw new Error(response.data['Error Message']); } const timeSeries = response.data['Time Series (Daily)']; if (!timeSeries) { throw new Error('No time series data found in the response'); } // Get dates sorted from newest to oldest const dates = Object.keys(timeSeries).sort().reverse(); if (dates.length < 2) { return `Not enough historical data available for ${symbolStr} to generate alerts.`; } let alerts = `Stock Alerts for ${symbolStr.toUpperCase()} (${threshold}% threshold):\n\n`; let alertCount = 0; // Analyze the last 10 days (or less if not available) const daysToAnalyze = Math.min(10, dates.length - 1); for (let i = 0; i < daysToAnalyze; i++) { const currentDate = dates[i]; const previousDate = dates[i + 1]; const currentClose = parseFloat(timeSeries[currentDate]['4. close']); const previousClose = parseFloat(timeSeries[previousDate]['4. close']); // Calculate percentage change const percentChange = ((currentClose - previousClose) / previousClose) * 100; const absPercentChange = Math.abs(percentChange); // Check if change exceeds threshold if (absPercentChange >= threshold) { const direction = percentChange >= 0 ? 'increased' : 'decreased'; alerts += `${currentDate}: Price ${direction} by ${absPercentChange.toFixed( 2, )}% from ${previousClose} to ${currentClose}\n`; alertCount++; } } if (alertCount === 0) { alerts += `No significant price movements (>=${threshold}%) detected in the last ${daysToAnalyze} trading days.\n`; } return alerts; } catch (error) { if (axios.isAxiosError(error)) { throw new Error(`API request failed: ${error.message}`); } throw error; } }