refactor(data): 重构交易所适配器,修复 Kline 实体复合主键

- 废弃旧 adapter 体系 (base/binance/types.ts),新增 base_rest/rest.ts
  基于 Binance 官方 SDK 实现 REST K 线拉取
- Kline 实体改为四列复合主键 (exchange/symbol/interval/time),
  修复单列 time PK 导致的跨 symbol 写入冲突
- 新增 filterConsecutive():K 线连续性过滤,首缺口截断策略
- 新增 service/kline.ts:批量 UPSERT K 线入库
- 新增 types/ 共享类型定义、example/ 示例、run/ 运行脚本
This commit is contained in:
Rekey
2026-06-08 18:18:16 +08:00
parent 85a0031a78
commit 5e385547c7
16 changed files with 829 additions and 1043 deletions
+109
View File
@@ -0,0 +1,109 @@
// ============================================================
// base.ts — REST 客户端抽象基类
// ============================================================
// 各交易所适配器继承此类,复用 REST 请求限流、重试等通用逻辑。
// 子类通过注入各交易所原生 SDKbinance / okx / bybit ...
// 实现具体的数据拉取,无需依赖 ccxt。
//
// 子类只需实现:
// - fetchKlines() — 历史 K 线拉取(基于目标交易所 SDK)
// - fetchMarkets() — 交易对元数据拉取(用于自动注册交易对)
//
// WebSocket 实时行情由各适配器自行管理,不在此基类范围内。
// ============================================================
import { logger } from "../utils/logger";
import type { Kline, KlineInterval, MarketInfo, RestClientConfig } from "../types";
import { DEFAULT_REST_CONFIG } from "../types/base";
// ============================================================
// 工具:异步 sleep
// ============================================================
/** 返回一个在 ms 毫秒后 resolve 的 Promise */
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// ============================================================
// BaseRestClient
// ============================================================
export abstract class BaseRestClient {
/** 交易所标识(子类必须覆盖) */
abstract readonly exchange: string;
/** REST 客户端配置(可在子类构造函数中覆盖默认值) */
protected readonly config: RestClientConfig;
/** REST 请求节流 Mapkey → 上次请求时间戳) */
private lastRestFetch = new Map<string, number>();
// ============================================================
// 构造函数
// ============================================================
constructor(config: Partial<RestClientConfig> = {}) {
this.config = { ...DEFAULT_REST_CONFIG, ...config };
}
// ============================================================
// 限流工具
// ============================================================
/**
* 对同一 key 的 REST 请求进行冷却节流。
* 若距离上次请求不足 restRateLimitMs,自动等待剩余时间。
*
* @param key - 节流标识(如 `${symbol}:${interval}`
*/
protected async throttle(key: string): Promise<void> {
const lastFetch = this.lastRestFetch.get(key) ?? 0;
const elapsed = Date.now() - lastFetch;
if (elapsed < this.config.restRateLimitMs) {
const waitMs = this.config.restRateLimitMs - elapsed;
logger.debug(
{ exchange: this.exchange, key, waitMs },
`[${this.exchange}] REST 限流等待 ${waitMs}ms`,
);
await sleep(waitMs);
}
this.lastRestFetch.set(key, Date.now());
}
// ============================================================
// 抽象方法 —— 子类必须使用各自的交易所 SDK 实现
// ============================================================
/**
* 拉取历史 K 线数据(REST)。
*
* 子类负责:
* 1. 调用交易所原生 SDK 的 K 线接口
* 2. 将原始数据转换为本系统标准化 Kline 结构
* 3. 处理分页逻辑(若时间跨度超过单次请求上限)
*
* @param symbol - 交易对符号(如 BTCUSDT
* @param interval - K 线周期
* @param startTime - 起始时间(Unix ms
* @param endTime - 结束时间(Unix ms
* @param limit - 单次最大条数(默认取自 config.defaultLimit
*/
abstract fetchKlines(
symbol: string,
interval: KlineInterval,
startTime: number,
endTime: number,
limit?: number,
): Promise<Kline[]>;
/**
* 获取交易所交易对信息(REST)。
*
* 子类负责调用交易所原生 SDK 的 /exchangeInfo 等价接口,
* 并转换为本系统标准化 MarketInfo 结构。
*/
abstract fetchMarkets(): Promise<MarketInfo[]>;
}
export default BaseRestClient;