import { Logger } from '@stock-bot/logger'; import { HttpClient } from '@stock-bot/http'; import createCache, { type CacheProvider } from '@stock-bot/cache'; export interface MarketDataResponse { symbol: string; price: number; timestamp: Date; volume?: number; change?: number; open?: number; high?: number; low?: number; close?: number; } export class MarketDataProvider { private logger = new Logger('market-data-provider'); private httpClient: HttpClient; private cache: CacheProvider = createCache('hybrid'); private readonly CACHE_TTL = 60; // 1 minute constructor() { this.httpClient = new HttpClient({ timeout: 10000, }, this.logger); } async getLiveData(symbol: string): Promise { const cacheKey = `market-data:${symbol}`; try { // Check cache first const cached = await this.cache.get(cacheKey) as MarketDataResponse | null; if (cached) { this.logger.debug('Returning cached market data', { symbol }); return cached; } // Generate simulated data for demo const data = this.generateSimulatedData(symbol); // Cache the result await this.cache.set(cacheKey, data, this.CACHE_TTL); this.logger.info('Generated live market data', { symbol, price: data.price }); return data; } catch (error) { this.logger.error('Error fetching market data', { symbol, error }); throw error; } } async getHistoricalData(symbol: string, from: Date, to: Date, interval: string = '1m'): Promise { const cacheKey = `historical:${symbol}:${from.toISOString()}:${to.toISOString()}:${interval}`; try { const cached = await this.cache.get(cacheKey) as MarketDataResponse[] | null; if (cached) { this.logger.debug('Returning cached historical data', { symbol, from, to }); return cached; } // Generate simulated historical data const data = this.generateHistoricalData(symbol, from, to, interval); // Cache for longer time (1 hour) await this.cache.set(cacheKey, data, 3600); this.logger.info('Generated historical market data', { symbol, from, to, count: data.length }); return data; } catch (error) { this.logger.error('Error fetching historical data', { symbol, error }); throw error; } } private generateSimulatedData(symbol: string): MarketDataResponse { // Base prices for different symbols const basePrices: { [key: string]: number } = { 'AAPL': 150, 'GOOGL': 2500, 'MSFT': 300, 'TSLA': 200, 'AMZN': 3000, 'NVDA': 400, 'META': 250, 'NFLX': 400 }; const basePrice = basePrices[symbol] || 100; // Add some randomness (+/- 2%) const variation = (Math.random() - 0.5) * 0.04; // ±2% const price = basePrice * (1 + variation); const change = variation * basePrice; return { symbol, price: Math.round(price * 100) / 100, timestamp: new Date(), volume: Math.floor(Math.random() * 1000000) + 500000, change: Math.round(change * 100) / 100, open: Math.round((price - change) * 100) / 100, high: Math.round((price + Math.abs(change * 0.5)) * 100) / 100, low: Math.round((price - Math.abs(change * 0.5)) * 100) / 100, close: Math.round(price * 100) / 100 }; } private generateHistoricalData(symbol: string, from: Date, to: Date, interval: string): MarketDataResponse[] { const data: MarketDataResponse[] = []; const intervalMs = this.parseInterval(interval); let currentTime = new Date(from); const endTime = new Date(to); // Base price for the symbol const basePrices: { [key: string]: number } = { 'AAPL': 150, 'GOOGL': 2500, 'MSFT': 300, 'TSLA': 200, 'AMZN': 3000, 'NVDA': 400, 'META': 250, 'NFLX': 400 }; let basePrice = basePrices[symbol] || 100; while (currentTime <= endTime) { // Add some trend and randomness const trend = (Math.random() - 0.5) * 0.01; // Small trend const variation = (Math.random() - 0.5) * 0.02; // Random variation basePrice = basePrice * (1 + trend + variation); const change = basePrice * variation; data.push({ symbol, price: Math.round(basePrice * 100) / 100, timestamp: new Date(currentTime), volume: Math.floor(Math.random() * 1000000) + 500000, change: Math.round(change * 100) / 100, open: Math.round((basePrice - change) * 100) / 100, high: Math.round((basePrice + Math.abs(change * 0.5)) * 100) / 100, low: Math.round((basePrice - Math.abs(change * 0.5)) * 100) / 100, close: Math.round(basePrice * 100) / 100 }); currentTime = new Date(currentTime.getTime() + intervalMs); } return data; } private parseInterval(interval: string): number { const value = parseInt(interval.slice(0, -1)); const unit = interval.slice(-1).toLowerCase(); switch (unit) { case 's': return value * 1000; case 'm': return value * 60 * 1000; case 'h': return value * 60 * 60 * 1000; case 'd': return value * 24 * 60 * 60 * 1000; default: return 60 * 1000; // Default to 1 minute } } async clearCache(): Promise { this.logger.info('Clearing market data cache'); // Note: Cache provider limitations - would need proper key tracking } async shutdown(): Promise { this.logger.info('Shutting down MarketDataProvider'); } } export const marketDataProvider = new MarketDataProvider();