refactor(data/exchanges): 多子类架构 + 工厂入口
- 每个交易所独立子类: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 调用方
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user