import { logger } from '../utils/logger' /** * Market Data Cache Service * * Purpose: Stores real-time TradingView metrics for manual trade validation. * Data flows: TradingView → /api/trading/market-data → Cache → Re-entry checks * * Cache expiry: 5 minutes (configurable) */ export interface MarketMetrics { symbol: string // "SOL-PERP", "ETH-PERP", "BTC-PERP" atr: number // Average True Range (volatility %) adx: number // Average Directional Index (trend strength) rsi: number // Relative Strength Index (momentum) volumeRatio: number // Current volume / average volume pricePosition: number // Position in recent range (0-100%) maGap?: number // MA50-MA200 gap percentage (v9+) currentPrice: number // Latest close price timestamp: number // Unix timestamp (ms) timeframe: string // "5" for 5min, "60" for 1h, etc. } class MarketDataCache { private cache: Map = new Map() private readonly MAX_AGE_MS = 5 * 60 * 1000 // 5 minutes /** * Store fresh market data from TradingView */ set(symbol: string, metrics: MarketMetrics): void { this.cache.set(symbol, metrics) logger.log( `📊 Cached market data for ${symbol}: ` + `ADX=${metrics.adx.toFixed(1)} ` + `ATR=${metrics.atr.toFixed(2)}% ` + `RSI=${metrics.rsi.toFixed(1)} ` + `Vol=${metrics.volumeRatio.toFixed(2)}x` ) } /** * Retrieve cached data if still fresh (<5min old) * Returns null if stale or missing */ get(symbol: string): MarketMetrics | null { const data = this.cache.get(symbol) if (!data) { logger.log(`⚠️ No cached data for ${symbol}`) return null } const ageSeconds = Math.round((Date.now() - data.timestamp) / 1000) if (Date.now() - data.timestamp > this.MAX_AGE_MS) { logger.log(`⏰ Cached data for ${symbol} is stale (${ageSeconds}s old, max 300s)`) return null } logger.log(`✅ Using fresh TradingView data for ${symbol} (${ageSeconds}s old)`) return data } /** * Check if fresh data exists without retrieving it */ has(symbol: string): boolean { const data = this.cache.get(symbol) if (!data) return false return Date.now() - data.timestamp <= this.MAX_AGE_MS } /** * Get all cached symbols with fresh data */ getAvailableSymbols(): string[] { const now = Date.now() const freshSymbols: string[] = [] for (const [symbol, data] of this.cache.entries()) { if (now - data.timestamp <= this.MAX_AGE_MS) { freshSymbols.push(symbol) } } return freshSymbols } /** * Get age of cached data in seconds (for debugging) */ getDataAge(symbol: string): number | null { const data = this.cache.get(symbol) if (!data) return null return Math.round((Date.now() - data.timestamp) / 1000) } /** * Clear all cached data (for testing) */ clear(): void { this.cache.clear() logger.log('🗑️ Market data cache cleared') } } // Singleton instance let marketDataCache: MarketDataCache | null = null export function getMarketDataCache(): MarketDataCache { if (!marketDataCache) { marketDataCache = new MarketDataCache() logger.log('🔧 Initialized Market Data Cache (5min expiry)') } return marketDataCache }