// ============================================================ // base.ts — REST 客户端抽象基类 // ============================================================ // 各交易所适配器继承此类,复用 REST 请求限流、重试等通用逻辑。 // 子类通过注入各交易所原生 SDK(binance / okx / bybit ...) // 实现具体的数据拉取,无需依赖 ccxt。 // // 子类只需实现: // - fetchKlines() — 历史 K 线拉取(基于目标交易所 SDK) // - fetchMarkets() — 交易对元数据拉取(用于自动注册交易对) // // WebSocket 实时行情由各适配器自行管理,不在此基类范围内。 // ============================================================ import { logger } from "../utils/logger"; import type { Kline, KlineInterval, MarketInfo, RestClientConfig } from "../types"; import { DEFAULT_REST_CONFIG } from "../types/base"; // ============================================================ // 工具:异步 sleep // ============================================================ /** 返回一个在 ms 毫秒后 resolve 的 Promise */ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } // ============================================================ // BaseRestClient // ============================================================ export abstract class BaseRestClient { /** 交易所标识(子类必须覆盖) */ abstract readonly exchange: string; /** REST 客户端配置(可在子类构造函数中覆盖默认值) */ protected readonly config: RestClientConfig; /** REST 请求节流 Map(key → 上次请求时间戳) */ private lastRestFetch = new Map(); // ============================================================ // 构造函数 // ============================================================ constructor(config: Partial = {}) { this.config = { ...DEFAULT_REST_CONFIG, ...config }; } // ============================================================ // 限流工具 // ============================================================ /** * 对同一 key 的 REST 请求进行冷却节流。 * 若距离上次请求不足 restRateLimitMs,自动等待剩余时间。 * * @param key - 节流标识(如 `${symbol}:${interval}`) */ protected async throttle(key: string): Promise { const lastFetch = this.lastRestFetch.get(key) ?? 0; const elapsed = Date.now() - lastFetch; if (elapsed < this.config.restRateLimitMs) { const waitMs = this.config.restRateLimitMs - elapsed; logger.debug( { exchange: this.exchange, key, waitMs }, `[${this.exchange}] REST 限流等待 ${waitMs}ms`, ); await sleep(waitMs); } this.lastRestFetch.set(key, Date.now()); } // ============================================================ // 抽象方法 —— 子类必须使用各自的交易所 SDK 实现 // ============================================================ /** * 拉取历史 K 线数据(REST)。 * * 子类负责: * 1. 调用交易所原生 SDK 的 K 线接口 * 2. 将原始数据转换为本系统标准化 Kline 结构 * 3. 处理分页逻辑(若时间跨度超过单次请求上限) * * @param symbol - 交易对符号(如 BTCUSDT) * @param interval - K 线周期 * @param startTime - 起始时间(Unix ms) * @param endTime - 结束时间(Unix ms) * @param limit - 单次最大条数(默认取自 config.defaultLimit) */ abstract fetchKlines( symbol: string, interval: KlineInterval, startTime: number, endTime: number, limit?: number, ): Promise; /** * 获取交易所交易对信息(REST)。 * * 子类负责调用交易所原生 SDK 的 /exchangeInfo 等价接口, * 并转换为本系统标准化 MarketInfo 结构。 */ abstract fetchMarkets(): Promise; } export default BaseRestClient;