marketDataCache.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. import { MarketData, Ticker24hr, KlineData, DepthData } from './marketDataManager'
  2. /**
  3. * 缓存配置
  4. */
  5. export interface CacheConfig {
  6. maxKlineRecords: number // 每个交易对每个时间间隔最大K线记录数
  7. maxDepthLevels: number // 深度数据最大档位数
  8. cleanupInterval: number // 清理间隔(毫秒)
  9. maxAge: number // 数据最大保存时间(毫秒)
  10. }
  11. /**
  12. * 行情数据缓存管理器
  13. * 用于优化内存使用和数据管理
  14. */
  15. export class MarketDataCache {
  16. private config: CacheConfig
  17. private cleanupTimer: NodeJS.Timeout | null = null
  18. // 缓存数据
  19. private marketDataCache: Map<string, MarketData> = new Map()
  20. private ticker24hrCache: Map<string, Ticker24hr> = new Map()
  21. private klineDataCache: Map<string, Map<string, KlineData[]>> = new Map()
  22. private depthDataCache: Map<string, DepthData> = new Map()
  23. // 数据时间戳
  24. private dataTimestamps: Map<string, number> = new Map()
  25. constructor(config: Partial<CacheConfig> = {}) {
  26. this.config = {
  27. maxKlineRecords: 1000,
  28. maxDepthLevels: 20,
  29. cleanupInterval: 60000, // 1分钟
  30. maxAge: 300000, // 5分钟
  31. ...config,
  32. }
  33. this.startCleanupTimer()
  34. }
  35. /**
  36. * 更新行情数据
  37. */
  38. public updateMarketData(data: MarketData): void {
  39. this.marketDataCache.set(data.symbol, data)
  40. this.dataTimestamps.set(`market_${data.symbol}`, Date.now())
  41. }
  42. /**
  43. * 更新24小时行情数据
  44. */
  45. public updateTicker24hr(data: Ticker24hr): void {
  46. this.ticker24hrCache.set(data.symbol, data)
  47. this.dataTimestamps.set(`ticker_${data.symbol}`, Date.now())
  48. }
  49. /**
  50. * 更新K线数据
  51. */
  52. public updateKlineData(data: KlineData): void {
  53. const symbol = data.symbol
  54. const interval = data.interval
  55. if (!this.klineDataCache.has(symbol)) {
  56. this.klineDataCache.set(symbol, new Map())
  57. }
  58. const symbolKlines = this.klineDataCache.get(symbol)!
  59. if (!symbolKlines.has(interval)) {
  60. symbolKlines.set(interval, [])
  61. }
  62. const klines = symbolKlines.get(interval)!
  63. // 查找是否已存在相同时间戳的K线
  64. const existingIndex = klines.findIndex(k => k.openTime === data.openTime)
  65. if (existingIndex >= 0) {
  66. klines[existingIndex] = data
  67. } else {
  68. klines.push(data)
  69. // 保持最大记录数限制
  70. if (klines.length > this.config.maxKlineRecords) {
  71. klines.splice(0, klines.length - this.config.maxKlineRecords)
  72. }
  73. }
  74. this.dataTimestamps.set(`kline_${symbol}_${interval}`, Date.now())
  75. }
  76. /**
  77. * 更新深度数据
  78. */
  79. public updateDepthData(data: DepthData): void {
  80. // 限制深度档位数
  81. const limitedData: DepthData = {
  82. symbol: data.symbol,
  83. bids: data.bids.slice(0, this.config.maxDepthLevels),
  84. asks: data.asks.slice(0, this.config.maxDepthLevels),
  85. lastUpdateId: data.lastUpdateId,
  86. }
  87. this.depthDataCache.set(data.symbol, limitedData)
  88. this.dataTimestamps.set(`depth_${data.symbol}`, Date.now())
  89. }
  90. /**
  91. * 获取行情数据
  92. */
  93. public getMarketData(symbol: string): MarketData | null {
  94. return this.marketDataCache.get(symbol) || null
  95. }
  96. /**
  97. * 获取所有行情数据
  98. */
  99. public getAllMarketData(): Map<string, MarketData> {
  100. return new Map(this.marketDataCache)
  101. }
  102. /**
  103. * 获取24小时行情数据
  104. */
  105. public getTicker24hr(symbol: string): Ticker24hr | null {
  106. return this.ticker24hrCache.get(symbol) || null
  107. }
  108. /**
  109. * 获取所有24小时行情数据
  110. */
  111. public getAllTicker24hr(): Map<string, Ticker24hr> {
  112. return new Map(this.ticker24hrCache)
  113. }
  114. /**
  115. * 获取K线数据
  116. */
  117. public getKlineData(symbol: string, interval: string, limit?: number): KlineData[] {
  118. const symbolKlines = this.klineDataCache.get(symbol)
  119. if (!symbolKlines) return []
  120. const klines = symbolKlines.get(interval)
  121. if (!klines) return []
  122. if (limit) {
  123. return klines.slice(-limit)
  124. }
  125. return [...klines]
  126. }
  127. /**
  128. * 获取深度数据
  129. */
  130. public getDepthData(symbol: string): DepthData | null {
  131. return this.depthDataCache.get(symbol) || null
  132. }
  133. /**
  134. * 获取缓存统计信息
  135. */
  136. public getCacheStats(): {
  137. marketDataCount: number
  138. ticker24hrCount: number
  139. klineDataCount: number
  140. depthDataCount: number
  141. totalMemoryUsage: number
  142. } {
  143. let klineDataCount = 0
  144. for (const symbolKlines of this.klineDataCache.values()) {
  145. for (const klines of symbolKlines.values()) {
  146. klineDataCount += klines.length
  147. }
  148. }
  149. return {
  150. marketDataCount: this.marketDataCache.size,
  151. ticker24hrCount: this.ticker24hrCache.size,
  152. klineDataCount,
  153. depthDataCount: this.depthDataCache.size,
  154. totalMemoryUsage: this.estimateMemoryUsage(),
  155. }
  156. }
  157. /**
  158. * 清理过期数据
  159. */
  160. public cleanup(): void {
  161. const now = Date.now()
  162. const expiredKeys: string[] = []
  163. // 检查过期数据
  164. for (const [key, timestamp] of this.dataTimestamps.entries()) {
  165. if (now - timestamp > this.config.maxAge) {
  166. expiredKeys.push(key)
  167. }
  168. }
  169. // 删除过期数据
  170. expiredKeys.forEach(key => {
  171. const [type, symbol, interval] = key.split('_')
  172. switch (type) {
  173. case 'market':
  174. this.marketDataCache.delete(symbol)
  175. break
  176. case 'ticker':
  177. this.ticker24hrCache.delete(symbol)
  178. break
  179. case 'kline':
  180. const symbolKlines = this.klineDataCache.get(symbol)
  181. if (symbolKlines) {
  182. symbolKlines.delete(interval)
  183. if (symbolKlines.size === 0) {
  184. this.klineDataCache.delete(symbol)
  185. }
  186. }
  187. break
  188. case 'depth':
  189. this.depthDataCache.delete(symbol)
  190. break
  191. }
  192. this.dataTimestamps.delete(key)
  193. })
  194. if (expiredKeys.length > 0) {
  195. console.log(`🧹 清理了 ${expiredKeys.length} 条过期数据`)
  196. }
  197. }
  198. /**
  199. * 清空所有缓存
  200. */
  201. public clear(): void {
  202. this.marketDataCache.clear()
  203. this.ticker24hrCache.clear()
  204. this.klineDataCache.clear()
  205. this.depthDataCache.clear()
  206. this.dataTimestamps.clear()
  207. console.log('🗑️ 已清空所有缓存数据')
  208. }
  209. /**
  210. * 获取指定交易对的所有数据
  211. */
  212. public getSymbolData(symbol: string): {
  213. marketData: MarketData | null
  214. ticker24hr: Ticker24hr | null
  215. klineData: Map<string, KlineData[]>
  216. depthData: DepthData | null
  217. } {
  218. return {
  219. marketData: this.getMarketData(symbol),
  220. ticker24hr: this.getTicker24hr(symbol),
  221. klineData: this.klineDataCache.get(symbol) || new Map(),
  222. depthData: this.getDepthData(symbol),
  223. }
  224. }
  225. /**
  226. * 检查数据是否过期
  227. */
  228. public isDataExpired(symbol: string, type: 'market' | 'ticker' | 'kline' | 'depth', interval?: string): boolean {
  229. const key = interval ? `${type}_${symbol}_${interval}` : `${type}_${symbol}`
  230. const timestamp = this.dataTimestamps.get(key)
  231. if (!timestamp) return true
  232. return Date.now() - timestamp > this.config.maxAge
  233. }
  234. /**
  235. * 获取数据更新时间
  236. */
  237. public getDataTimestamp(
  238. symbol: string,
  239. type: 'market' | 'ticker' | 'kline' | 'depth',
  240. interval?: string,
  241. ): number | null {
  242. const key = interval ? `${type}_${symbol}_${interval}` : `${type}_${symbol}`
  243. return this.dataTimestamps.get(key) || null
  244. }
  245. /**
  246. * 启动清理定时器
  247. */
  248. private startCleanupTimer(): void {
  249. this.cleanupTimer = setInterval(() => {
  250. this.cleanup()
  251. }, this.config.cleanupInterval)
  252. }
  253. /**
  254. * 停止清理定时器
  255. */
  256. public stopCleanupTimer(): void {
  257. if (this.cleanupTimer) {
  258. clearInterval(this.cleanupTimer)
  259. this.cleanupTimer = null
  260. }
  261. }
  262. /**
  263. * 估算内存使用量
  264. */
  265. private estimateMemoryUsage(): number {
  266. let totalSize = 0
  267. // 估算 Map 开销
  268. totalSize += this.marketDataCache.size * 200 // 每个 MarketData 约 200 字节
  269. totalSize += this.ticker24hrCache.size * 300 // 每个 Ticker24hr 约 300 字节
  270. totalSize += this.depthDataCache.size * 400 // 每个 DepthData 约 400 字节
  271. // 估算 K线数据
  272. for (const symbolKlines of this.klineDataCache.values()) {
  273. for (const klines of symbolKlines.values()) {
  274. totalSize += klines.length * 150 // 每个 KlineData 约 150 字节
  275. }
  276. }
  277. // 估算时间戳 Map
  278. totalSize += this.dataTimestamps.size * 50 // 每个时间戳约 50 字节
  279. return totalSize
  280. }
  281. /**
  282. * 销毁缓存管理器
  283. */
  284. public destroy(): void {
  285. this.stopCleanupTimer()
  286. this.clear()
  287. }
  288. }