UNPKG

fitr

Version:
138 lines (117 loc) 5.62 kB
import yahooFinance from 'yahoo-finance2'; import { format, parse, startOfWeek, addDays, parseISO, isBefore, getDay, isEqual } from 'date-fns'; import { PricePoint, PriceSource } from '../types'; import { Logger } from '../utils/logger'; /** * Get historical weekly price data for a symbol between two dates */ export async function getHistoricalPrices( symbol: string, fromDate: string, toDate: string = format(new Date(), 'yyyy-MM-dd'), interval: '1wk' | '1d' = '1wk' ): Promise<PricePoint[]> { try { Logger.info(`Fetching historical data for ${symbol} from ${fromDate} to ${toDate}...`); const from = parse(fromDate, 'yyyy-MM-dd', new Date()); const to = parse(toDate, 'yyyy-MM-dd', new Date()); // Ensure period1 and period2 are not the same date // If they are the same, add one day to period2 const adjustedTo = isEqual(from, to) ? addDays(to, 1) : to; if (isEqual(from, to)) { Logger.debug(`fromDate and toDate are the same (${fromDate}). Adjusting toDate to the next day.`); } // Use chart() instead of historical() as recommended by yahoo-finance2 const chartResult = await yahooFinance.chart(symbol, { period1: from, period2: adjustedTo, interval: interval // Weekly or daily data }); // Map the data to our PricePoint format const pricePoints: PricePoint[] = chartResult.quotes .filter(quote => quote.close !== null) // Filter out null values .map(quote => ({ date: format(new Date(quote.date), 'yyyy-MM-dd'), price: quote.close as number, // We've filtered out null values source: PriceSource.YAHOO })); Logger.debug(`Found ${pricePoints.length} historical price points for ${symbol}`); return pricePoints; } catch (error) { Logger.error(`Error fetching historical prices for ${symbol}`, error); return []; } } /** * Get the next occurrence of a specific day of the week after a given date * @param date The starting date * @param dayOfWeek The day of the week to find (0 = Sunday, 1 = Monday, etc.) * @returns The next occurrence of the specified day */ export function getNextDayOfWeek(date: Date, dayOfWeek: 0 | 1 | 2 | 3 | 4 | 5 | 6): Date { const currentDay = getDay(date); // Calculate days to add let daysToAdd = dayOfWeek - currentDay; if (daysToAdd <= 0) { // If the day has already occurred this week, go to next week daysToAdd += 7; } return addDays(date, daysToAdd); } /** * Get weekly prices for a symbol since a given date * * @param symbol The stock/ETF symbol * @param sinceDate The date to start fetching prices from (exclusive) * @param weekStartsOn The day of the week to consider as the first day (0 = Sunday, 1 = Monday, etc.) * @returns Array of price points */ export async function getWeeklyPrices( symbol: string, sinceDate: string, weekStartsOn: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 1 // Default to Monday ): Promise<PricePoint[]> { try { const today = new Date(); const sinceDateObj = parseISO(sinceDate); // Debug logging Logger.debug(`Getting weekly prices for ${symbol} since ${sinceDate}`); Logger.debug(`Today: ${format(today, 'yyyy-MM-dd')}`); Logger.debug(`Week starts on day: ${weekStartsOn} (0=Sunday, 1=Monday, ...)`); // Get the start of the current week const currentWeekStart = startOfWeek(today, { weekStartsOn }); Logger.debug(`Current week start: ${format(currentWeekStart, 'yyyy-MM-dd')}`); // If the sinceDate is after or on the current week's start, no need to fetch new data if (!isBefore(sinceDateObj, currentWeekStart)) { Logger.info(`Last update for ${symbol} was already in the current week. No new data needed.`); return []; } // Find the next occurrence of the specified weekday after sinceDate const nextWeekday = getNextDayOfWeek(sinceDateObj, weekStartsOn); Logger.debug(`Next ${weekStartsOn} after ${sinceDate} is ${format(nextWeekday, 'yyyy-MM-dd')}`); // Format dates for API call const fromDate = format(nextWeekday, 'yyyy-MM-dd'); const toDate = format(today, 'yyyy-MM-dd'); // Fetch historical prices const allPrices = await getHistoricalPrices(symbol, fromDate, toDate, '1wk'); // Filter to only include prices for the specified day of the week // or include all if we don't have enough data if (allPrices.length > 7) { const filteredPrices = allPrices.filter(price => { const priceDate = parseISO(price.date); return getDay(priceDate) === weekStartsOn; }); // If we have filtered prices, return those, otherwise return all prices if (filteredPrices.length > 0) { Logger.debug(`Filtered to ${filteredPrices.length} prices for day ${weekStartsOn} of the week`); return filteredPrices; } } // If we don't have enough data or no filtered prices, return all prices Logger.info(`Returning all ${allPrices.length} price points (not enough data to filter by day of week)`); return allPrices; } catch (error) { Logger.error(`Error getting weekly prices for ${symbol}`, error); return []; } }