ebaef5042e
- 新增 FetchKlinesParams 接口统一 fetchKlines 参数(exchange/type/symbol/startTime/limit/endTime) - BaseRestClient 抽象方法 + MarketDataFeed 接口改为 params 对象签名 - BinanceRestClient / BinanceFuturesRestClient 由调用方传入 type,不再硬编码 spot/um - index.ts 新增静态 fetchKlines(params) 便捷函数,按 exchange+type 自动路由 - exchange.ts / bnkline.ts 调用方适配新签名 - 初始化 SQL 补充 BNBUSDT/SOLUSDT 合约交易对
197 lines
6.1 KiB
TypeScript
197 lines
6.1 KiB
TypeScript
import { MainClient, USDMClient, type Kline as BinanceRestKline } from "binance";
|
|
|
|
import { logger } from "../../utils/logger";
|
|
import { exchange } from "../../config";
|
|
import { BaseRestClient } from "../base";
|
|
import type { Kline, MarketInfo, KlineInterval, PairType, FetchKlinesParams } 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,
|
|
type: PairType,
|
|
): Kline {
|
|
const [
|
|
openTime,
|
|
open,
|
|
high,
|
|
low,
|
|
close,
|
|
volume,
|
|
closeTime,
|
|
quoteVolume,
|
|
tradeCount,
|
|
takerBuyBaseVol,
|
|
takerBuyQuoteVol,
|
|
// [11] ignore — 丢弃
|
|
] = raw;
|
|
|
|
return {
|
|
exchange: "binance",
|
|
symbol,
|
|
type,
|
|
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 params - fetchKlines 统一参数对象
|
|
*/
|
|
async fetchKlines(params: FetchKlinesParams): Promise<Kline[]> {
|
|
const { symbol, startTime, limit, endTime, type } = params;
|
|
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", type))
|
|
.sort((a, b) => a.openTime - b.openTime);
|
|
}
|
|
|
|
async fetchMarkets(): Promise<MarketInfo[]> {
|
|
// TODO: 调用 Binance /exchangeInfo 接口
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// BinanceFuturesRestClient — USDT-M 永续合约
|
|
// ============================================================
|
|
|
|
export class BinanceFuturesRestClient extends BaseRestClient {
|
|
readonly exchange = "binance_futures";
|
|
|
|
private client: USDMClient;
|
|
|
|
constructor() {
|
|
super();
|
|
this.client = new USDMClient(
|
|
{
|
|
api_key: exchange.binanceFutures.apiKey,
|
|
api_secret: exchange.binanceFutures.apiSecret,
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 拉取 USDT-M 永续合约 1m K 线。
|
|
*
|
|
* USDMClient.getKlines() 返回与 MainClient 同构的 12 元组,
|
|
* convertBinanceKline 直接复用,type 由调用方通过 params 传入。
|
|
*/
|
|
async fetchKlines(params: FetchKlinesParams): Promise<Kline[]> {
|
|
const { symbol, startTime, limit, endTime, type } = params;
|
|
const effectiveLimit = limit ?? this.config.defaultLimit;
|
|
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_futures] fetchKlines 请求参数",
|
|
);
|
|
|
|
if (!rawKlines || rawKlines.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return rawKlines
|
|
.map((k) => convertBinanceKline(k, symbol, "1m", type))
|
|
.sort((a, b) => a.openTime - b.openTime);
|
|
}
|
|
|
|
async fetchMarkets(): Promise<MarketInfo[]> {
|
|
return [];
|
|
}
|
|
}
|