1adb093100
- 每个交易所独立子类:BinanceRestClient extends BaseRestClient - 统一入口 createRestClient(exchangeId) 工厂函数 - KLINE_INTERVAL_MS 移至 constants.ts - fetchKlines 签名统一为 (symbol, startTime, limit?, endTime?) - throttle 限流实际生效 - convertBinanceKline interval 参数化 - 删除 filterConsecutive 死代码 - 更新 run/exchange.ts 和 service/bnkline.ts 调用方
142 lines
4.5 KiB
TypeScript
142 lines
4.5 KiB
TypeScript
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<Kline[]> {
|
||
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<MarketInfo[]> {
|
||
// TODO: 调用 Binance /exchangeInfo 接口
|
||
return [];
|
||
}
|
||
}
|