feat(data): 实现 Binance WebSocket 适配器与架构重构

- 新增 exchanges/ 模块:MarketDataFeed 统一接口、BaseExchangeAdapter 抽象基类、
  BinanceAdapter 完整实现(WebSocket + REST)
- WebSocket 层基于 binance 官方 SDK 的 WebsocketClient,自动多路复用与断线重连
- REST 层使用 MainClient(Spot),实现 fetchKlines 自动分页补拉 + fetchMarkets 元数据解析
- 数据标准化:Ticker/Trade/OrderBook/Kline 类型定义与 Binance 原生格式互转
- 引入 RxJS Subject 作为统一事件流管道,按 eventType 运行时路由分发
- 重构 config/:YAML 驱动配置加载 + 零依赖运行时校验(fail-fast)
- 重构 db/:TypeORM DataSource 配置 + TimescaleDB K 线 Hypertable 实体
- 新增 utils/logger.ts:Pino 结构化日志(开发环境 pino-pretty 彩色输出)
- 新增 env.yaml 作为 TS/Python 共享的统一环境配置源
- 删除旧版手写 SQL schema 与散落配置文件,收敛到 TypeORM 实体管理
- 安装 rxjs@7.8.2 依赖
This commit is contained in:
Rekey
2026-06-08 01:24:48 +08:00
parent e91cad79e6
commit 85a0031a78
31 changed files with 4261 additions and 8757 deletions
+138
View File
@@ -0,0 +1,138 @@
// ============================================================
// kline.entity.ts — TimescaleDB K 线 Hypertable 实体
// ============================================================
// 映射到 PostgreSQL klines 表(TimescaleDB hypertable)。
// 不继承 CommonBaseEntity — 使用 @timescaledb/typeorm 的
// @Hypertable / @TimeColumn 装饰器管理 TimescaleDB 特性。
//
// 关键 TimescaleDB 特性(由 @Hypertable 装饰器自动配置):
// - 自动按 time 列做时间分区(by_range
// - 列式压缩(compress),7 天后自动执行
// - 通过 ContinuousAggregate 生成高周期 K 线视图
//
// 注意:@timescaledb/typeorm v0.0.1 为实验版本,
// 不支持空间分区(partitioning_column)。
// 若需要空间分区,可通过 db/init-db/ 下的 SQL 脚本手动添加。
// ============================================================
import { Hypertable, TimeColumn } from "@timescaledb/typeorm";
import {
Entity,
PrimaryColumn,
Column,
Index,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
/** K 线周期枚举 */
export type KlineInterval =
| "1m"
| "5m"
| "15m"
| "30m"
| "1h"
| "4h"
| "1d"
| "1w";
/**
* 1 分钟 K 线 Hypertable
*
* 存储交易所推送的 OHLCV 数据。写入使用 UPSERT
* ON CONFLICT DO UPDATE),已存在的 K 线只更新
* high/low/close/volume 增量。
*
* 高周期 K 线(5m+)通过 TimescaleDB 连续聚合视图
* 从 1m 表自动生成,无需单独建表。
*/
@Hypertable({
compression: {
compress: true,
compress_orderby: "time DESC",
compress_segmentby: "exchange, symbol, interval",
policy: {
schedule_interval: "365 days", // 365 天后自动压缩
},
},
})
@Index(["exchange", "symbol", "interval", "time"], { unique: true })
@Entity("klines")
export class Kline {
/** K 线开盘时间(UTC)— @timescaledb/typeorm 自动标记为时间分区列 */
@TimeColumn()
@PrimaryColumn("timestamptz")
time!: Date;
/** 交易所标识(binance / okx / bybit */
@Column("text")
exchange!: string;
/** 交易对符号(如 BTCUSDT */
@Column("text")
symbol!: string;
/** K 线周期(1m */
@Column("text")
interval!: KlineInterval;
// ============================================================
// OHLCV 价格数据(NUMERIC(20,8) 精度,与交易所对齐)
// ============================================================
/** 开盘价 */
@Column("numeric", { precision: 20, scale: 8 })
open!: number;
/** 最高价 */
@Column("numeric", { precision: 20, scale: 8 })
high!: number;
/** 最低价 */
@Column("numeric", { precision: 20, scale: 8 })
low!: number;
/** 收盘价 */
@Column("numeric", { precision: 20, scale: 8 })
close!: number;
/** 成交量(base 币种) */
@Column("numeric", { precision: 20, scale: 8 })
volume!: number;
// ============================================================
// 扩展字段(Binance 等交易所提供)
// ============================================================
/** 成交额(quote 币种) */
@Column("numeric", { precision: 20, scale: 8, nullable: true })
quote_volume?: number;
/** 主动买入成交量(base 币种) */
@Column("numeric", { precision: 20, scale: 8, nullable: true })
taker_buy_base_vol?: number;
/** 主动买入成交额(quote 币种) */
@Column("numeric", { precision: 20, scale: 8, nullable: true })
taker_buy_quote_vol?: number;
/** 成交笔数 */
@Column("integer", { nullable: true })
trade_count?: number;
/** K 线是否已关闭(true = 该周期 K 线不再变化) */
@Column("boolean", { default: true })
is_closed!: boolean;
// ============================================================
// 审计字段
// ============================================================
/** 记录首次写入时间 */
@CreateDateColumn({ type: "timestamptz", name: "created_at" })
createdAt!: Date;
/** 记录最后更新时间 */
@UpdateDateColumn({ type: "timestamptz", name: "updated_at" })
updatedAt!: Date;
}