import { MarketData, Ticker24hr, KlineData, DepthData } from './marketDataManager' /** * 缓存配置 */ export interface CacheConfig { maxKlineRecords: number // 每个交易对每个时间间隔最大K线记录数 maxDepthLevels: number // 深度数据最大档位数 cleanupInterval: number // 清理间隔(毫秒) maxAge: number // 数据最大保存时间(毫秒) } /** * 行情数据缓存管理器 * 用于优化内存使用和数据管理 */ export class MarketDataCache { private config: CacheConfig private cleanupTimer: NodeJS.Timeout | null = null // 缓存数据 private marketDataCache: Map = new Map() private ticker24hrCache: Map = new Map() private klineDataCache: Map> = new Map() private depthDataCache: Map = new Map() // 数据时间戳 private dataTimestamps: Map = new Map() constructor(config: Partial = {}) { this.config = { maxKlineRecords: 1000, maxDepthLevels: 20, cleanupInterval: 60000, // 1分钟 maxAge: 300000, // 5分钟 ...config, } this.startCleanupTimer() } /** * 更新行情数据 */ public updateMarketData(data: MarketData): void { this.marketDataCache.set(data.symbol, data) this.dataTimestamps.set(`market_${data.symbol}`, Date.now()) } /** * 更新24小时行情数据 */ public updateTicker24hr(data: Ticker24hr): void { this.ticker24hrCache.set(data.symbol, data) this.dataTimestamps.set(`ticker_${data.symbol}`, Date.now()) } /** * 更新K线数据 */ public updateKlineData(data: KlineData): void { const symbol = data.symbol const interval = data.interval if (!this.klineDataCache.has(symbol)) { this.klineDataCache.set(symbol, new Map()) } const symbolKlines = this.klineDataCache.get(symbol)! if (!symbolKlines.has(interval)) { symbolKlines.set(interval, []) } const klines = symbolKlines.get(interval)! // 查找是否已存在相同时间戳的K线 const existingIndex = klines.findIndex(k => k.openTime === data.openTime) if (existingIndex >= 0) { klines[existingIndex] = data } else { klines.push(data) // 保持最大记录数限制 if (klines.length > this.config.maxKlineRecords) { klines.splice(0, klines.length - this.config.maxKlineRecords) } } this.dataTimestamps.set(`kline_${symbol}_${interval}`, Date.now()) } /** * 更新深度数据 */ public updateDepthData(data: DepthData): void { // 限制深度档位数 const limitedData: DepthData = { symbol: data.symbol, bids: data.bids.slice(0, this.config.maxDepthLevels), asks: data.asks.slice(0, this.config.maxDepthLevels), lastUpdateId: data.lastUpdateId, } this.depthDataCache.set(data.symbol, limitedData) this.dataTimestamps.set(`depth_${data.symbol}`, Date.now()) } /** * 获取行情数据 */ public getMarketData(symbol: string): MarketData | null { return this.marketDataCache.get(symbol) || null } /** * 获取所有行情数据 */ public getAllMarketData(): Map { return new Map(this.marketDataCache) } /** * 获取24小时行情数据 */ public getTicker24hr(symbol: string): Ticker24hr | null { return this.ticker24hrCache.get(symbol) || null } /** * 获取所有24小时行情数据 */ public getAllTicker24hr(): Map { return new Map(this.ticker24hrCache) } /** * 获取K线数据 */ public getKlineData(symbol: string, interval: string, limit?: number): KlineData[] { const symbolKlines = this.klineDataCache.get(symbol) if (!symbolKlines) return [] const klines = symbolKlines.get(interval) if (!klines) return [] if (limit) { return klines.slice(-limit) } return [...klines] } /** * 获取深度数据 */ public getDepthData(symbol: string): DepthData | null { return this.depthDataCache.get(symbol) || null } /** * 获取缓存统计信息 */ public getCacheStats(): { marketDataCount: number ticker24hrCount: number klineDataCount: number depthDataCount: number totalMemoryUsage: number } { let klineDataCount = 0 for (const symbolKlines of this.klineDataCache.values()) { for (const klines of symbolKlines.values()) { klineDataCount += klines.length } } return { marketDataCount: this.marketDataCache.size, ticker24hrCount: this.ticker24hrCache.size, klineDataCount, depthDataCount: this.depthDataCache.size, totalMemoryUsage: this.estimateMemoryUsage(), } } /** * 清理过期数据 */ public cleanup(): void { const now = Date.now() const expiredKeys: string[] = [] // 检查过期数据 for (const [key, timestamp] of this.dataTimestamps.entries()) { if (now - timestamp > this.config.maxAge) { expiredKeys.push(key) } } // 删除过期数据 expiredKeys.forEach(key => { const [type, symbol, interval] = key.split('_') switch (type) { case 'market': this.marketDataCache.delete(symbol) break case 'ticker': this.ticker24hrCache.delete(symbol) break case 'kline': const symbolKlines = this.klineDataCache.get(symbol) if (symbolKlines) { symbolKlines.delete(interval) if (symbolKlines.size === 0) { this.klineDataCache.delete(symbol) } } break case 'depth': this.depthDataCache.delete(symbol) break } this.dataTimestamps.delete(key) }) if (expiredKeys.length > 0) { console.log(`🧹 清理了 ${expiredKeys.length} 条过期数据`) } } /** * 清空所有缓存 */ public clear(): void { this.marketDataCache.clear() this.ticker24hrCache.clear() this.klineDataCache.clear() this.depthDataCache.clear() this.dataTimestamps.clear() console.log('🗑️ 已清空所有缓存数据') } /** * 获取指定交易对的所有数据 */ public getSymbolData(symbol: string): { marketData: MarketData | null ticker24hr: Ticker24hr | null klineData: Map depthData: DepthData | null } { return { marketData: this.getMarketData(symbol), ticker24hr: this.getTicker24hr(symbol), klineData: this.klineDataCache.get(symbol) || new Map(), depthData: this.getDepthData(symbol), } } /** * 检查数据是否过期 */ public isDataExpired(symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string): boolean { const key = interval ? `${type}_${symbol}_${interval}` : `${type}_${symbol}` const timestamp = this.dataTimestamps.get(key) if (!timestamp) return true return Date.now() - timestamp > this.config.maxAge } /** * 获取数据更新时间 */ public getDataTimestamp( symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string, ): number | null { const key = interval ? `${type}_${symbol}_${interval}` : `${type}_${symbol}` return this.dataTimestamps.get(key) || null } /** * 启动清理定时器 */ private startCleanupTimer(): void { this.cleanupTimer = setInterval(() => { this.cleanup() }, this.config.cleanupInterval) } /** * 停止清理定时器 */ public stopCleanupTimer(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer) this.cleanupTimer = null } } /** * 估算内存使用量 */ private estimateMemoryUsage(): number { let totalSize = 0 // 估算 Map 开销 totalSize += this.marketDataCache.size * 200 // 每个 MarketData 约 200 字节 totalSize += this.ticker24hrCache.size * 300 // 每个 Ticker24hr 约 300 字节 totalSize += this.depthDataCache.size * 400 // 每个 DepthData 约 400 字节 // 估算 K线数据 for (const symbolKlines of this.klineDataCache.values()) { for (const klines of symbolKlines.values()) { totalSize += klines.length * 150 // 每个 KlineData 约 150 字节 } } // 估算时间戳 Map totalSize += this.dataTimestamps.size * 50 // 每个时间戳约 50 字节 return totalSize } /** * 销毁缓存管理器 */ public destroy(): void { this.stopCleanupTimer() this.clear() } }