import { MainClient, type Kline as BinanceRestKline } from "binance"; import { logger } from "../../utils/logger"; import { exchange } from "../../config"; import { BaseRestClient } from "../base"; import type { Kline, MarketInfo, KlineInterval } from "../../types"; // ============================================================ // Binance REST K 线 → 本系统标准化 Kline 转换 // ============================================================ /** * Binance SDK Kline 元组格式(getKlines / getUIKlines 返回): * [0] openTime: number — 开盘时间(Unix ms) * [1] open: numberInString — 开盘价 * [2] high: numberInString — 最高价 * [3] low: numberInString — 最低价 * [4] close: numberInString — 收盘价 * [5] volume: numberInString — 成交量(base 币种) * [6] closeTime: number — 收盘时间(Unix ms) * [7] quoteVolume: numberInString — 成交额(quote 币种) * [8] tradeCount: number — 成交笔数 * [9] takerBuyBaseVol: numberInString — 主动买入成交量 * [10] takerBuyQuoteVol: numberInString — 主动买入成交额 * [11] ignore: numberInString — 忽略字段 * * numberInString = string | number,通过 String() 统一转换。 * * 参考:node_modules/binance/lib/types/shared.d.ts:85-98 */ function convertBinanceKline( raw: BinanceRestKline, symbol: string, interval: KlineInterval, ): Kline { const [ openTime, open, high, low, close, volume, closeTime, quoteVolume, tradeCount, takerBuyBaseVol, takerBuyQuoteVol, // [11] ignore — 丢弃 ] = raw; return { exchange: "binance", symbol, interval, openTime, closeTime, open: String(open), high: String(high), low: String(low), close: String(close), volume: String(volume), quoteVolume: String(quoteVolume), takerBuyBaseVol: String(takerBuyBaseVol), takerBuyQuoteVol: String(takerBuyQuoteVol), tradeCount: String(tradeCount), isClosed: true, // REST 返回的 K 线均为已闭合历史数据 }; } // ============================================================ // BinanceRestClient // ============================================================ export class BinanceRestClient extends BaseRestClient { readonly exchange = "binance"; private client: MainClient; constructor() { super(); this.client = new MainClient( { api_key: exchange.binance.apiKey, api_secret: exchange.binance.apiSecret, }, { timeout: 3000 }, ); } /** * 拉取 1m K 线并转换为本系统标准化 Kline。 * * Binance 硬限制单次最多 1000 条,超限自动裁切。 * 高周期 K 线通过 TimescaleDB 连续聚合视图生成。 * * @param symbol - 交易对(如 BTCUSDT) * @param startTime - 起始时间(Unix ms) * @param limit - 单次拉取条数,默认取自 config.defaultLimit * @param endTime - 结束时间(Unix ms),可选 */ async fetchKlines( symbol: string, startTime: number, limit?: number, endTime?: number, ): Promise { const effectiveLimit = limit ?? this.config.defaultLimit; // Binance 硬限制:单次最多 1000 条 const safeLimit = Math.min(effectiveLimit, 1000); // 限流 await this.throttle(`${symbol}:1m`); const rawKlines = await this.client.getKlines({ symbol, interval: "1m", startTime, endTime, limit: safeLimit, }); logger.debug( { symbol, interval: "1m", startTime, endTime, limit: safeLimit }, "[binance] fetchKlines 请求参数", ); if (!rawKlines || rawKlines.length === 0) { return []; } // 按 openTime 升序排序(防御性),转换为标准化 Kline return rawKlines .map((k) => convertBinanceKline(k, symbol, "1m")) .sort((a, b) => a.openTime - b.openTime); } async fetchMarkets(): Promise { // TODO: 调用 Binance /exchangeInfo 接口 return []; } }