// ============================================================ // base.ts — 交易所适配器抽象基类 // ============================================================ // 所有交易所适配器(Binance / OKX / Bybit ...)继承此类, // 复用指数退避重连、连接状态管理、限流等通用逻辑。 // // 子类只需实现: // - connect() — 建立 WebSocket/REST 连接 // - disconnect() — 断开连接并清理资源 // - subscribeTicker() / subscribeTrade() / subscribeOrderbook() // - fetchKlines() — REST 历史 K 线补拉 // - fetchMarkets() — 交易对元数据拉取 // ============================================================ import { Subject, type Observable } from "rxjs"; import { logger } from "../utils/logger"; import type { MarketDataFeed, Ticker, Trade, OrderBook, Kline, KlineInterval, MarketInfo, ConnectionState, AdapterConfig, } from "./types"; import { DEFAULT_ADAPTER_CONFIG } from "./types"; // ============================================================ // 工具:异步 sleep // ============================================================ /** 返回一个在 ms 毫秒后 resolve 的 Promise */ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } // ============================================================ // BaseExchangeAdapter // ============================================================ export abstract class BaseExchangeAdapter implements MarketDataFeed { /** 交易所标识(子类必须覆盖) */ abstract readonly exchange: string; /** 适配器配置(可在子类构造函数中覆盖默认值) */ protected readonly config: AdapterConfig; /** 当前连接状态 */ protected _connectionState: ConnectionState = "disconnected"; /** 当前重连尝试次数(成功连接后重置) */ protected reconnectAttempt = 0; /** Subject 清理注册表 —— disconnect 时统一 complete */ protected activeSubjects = new Set>(); // ============================================================ // 构造函数 // ============================================================ constructor(config: Partial = {}) { this.config = { ...DEFAULT_ADAPTER_CONFIG, ...config }; } // ============================================================ // 连接状态(只读暴露) // ============================================================ get connectionState(): ConnectionState { return this._connectionState; } /** 更新连接状态并记录日志 */ protected setConnectionState(state: ConnectionState): void { const prev = this._connectionState; this._connectionState = state; if (prev !== state) { logger.info( { exchange: this.exchange, from: prev, to: state }, `[${this.exchange}] connection state: ${prev} → ${state}`, ); } } // ============================================================ // 指数退避重连(所有子类复用) // ============================================================ /** * 执行指数退避重连。 * * 延迟公式:delay = baseDelay × 2^min(attempt, 5) * - attempt=0: 3s * - attempt=1: 6s * - attempt=2: 12s * - attempt=5: 96s(之后不再翻倍) * * 超过 maxReconnectAttempts 后抛出错误。 * * @throws 达到最大重试次数后抛出 */ protected async reconnect(): Promise { const { reconnectBaseDelayMs: baseDelay, maxReconnectAttempts } = this.config; if (this.reconnectAttempt >= maxReconnectAttempts) { this.setConnectionState("error"); throw new Error( `[${this.exchange}] 重连失败:已达最大重试次数 (${maxReconnectAttempts})`, ); } const cappedAttempt = Math.min(this.reconnectAttempt, 5); const delay = baseDelay * Math.pow(2, cappedAttempt); logger.warn( { exchange: this.exchange, attempt: this.reconnectAttempt + 1, maxAttempts: maxReconnectAttempts, delayMs: delay, }, `[${this.exchange}] WebSocket 重连中...`, ); await sleep(delay); this.reconnectAttempt++; this.setConnectionState("connecting"); await this.connect(); } /** 成功连接后重置重连计数器 */ protected resetReconnectAttempts(): void { if (this.reconnectAttempt > 0) { logger.info( { exchange: this.exchange, attempts: this.reconnectAttempt }, `[${this.exchange}] 重连成功,计数器重置`, ); } this.reconnectAttempt = 0; } // ============================================================ // Subject 管理工具 // ============================================================ /** * 创建一个受管理的 Subject,disconnect 时自动 complete。 * 子类在 subscribe* 方法中使用此工具创建 Subject。 */ protected createManagedSubject(): Subject { const subject = new Subject(); this.activeSubjects.add(subject as Subject); return subject; } /** 完成所有受管理的 Subject(disconnect 时调用) */ protected completeAllSubjects(): void { for (const subject of this.activeSubjects) { subject.complete(); } this.activeSubjects.clear(); } // ============================================================ // 抽象方法 —— 子类必须实现 // ============================================================ abstract connect(): Promise; abstract disconnect(): Promise; abstract subscribeTicker(symbols: string[]): Observable; abstract subscribeTrade(symbols: string[]): Observable; abstract subscribeOrderbook(symbol: string, depth?: number): Observable; abstract fetchKlines( symbol: string, interval: KlineInterval, startTime: number, endTime: number, limit?: number, ): Promise; abstract fetchMarkets(): Promise; } export default BaseExchangeAdapter;