705a2f6ea0
- PairType 定义移至 types/kline.ts (spot/um/cm) - Kline 接口新增 type 字段,全链路透传 - klines 5列复合主键 (exchange, symbol, type, interval, time) - 拆出 BinanceFuturesRestClient (USDMClient) - exchanges/index.ts 注册 binance_futures - trading_pairs 唯一约束加 type,种子数据加合约对 - 12个连续聚合视图 SELECT/GROUP BY/INDEX 加 type - 清理 bnkline.ts 废弃代码和 pair.ts 空函数
208 lines
6.3 KiB
TypeScript
208 lines
6.3 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 } 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 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", "spot"))
|
||
.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 固定为 'um'。
|
||
*/
|
||
async fetchKlines(
|
||
symbol: string,
|
||
startTime: number,
|
||
limit?: number,
|
||
endTime?: number,
|
||
): Promise<Kline[]> {
|
||
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", "um"))
|
||
.sort((a, b) => a.openTime - b.openTime);
|
||
}
|
||
|
||
async fetchMarkets(): Promise<MarketInfo[]> {
|
||
return [];
|
||
}
|
||
}
|