// ============================================================ // 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, CreateDateColumn, UpdateDateColumn, } from "typeorm"; import type { KlineInterval } from '../../types'; /** * 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 天后自动压缩 }, }, }) @Entity("klines") export class Kline { /** * 复合主键:交易所 + 交易对 + 周期 + 时间 * * 设计原因: * - 不同 symbol 在同一时刻有各自的 K 线,单列 time PK 会导致跨 symbol 冲突 * - 四列复合主键 = 业务唯一性语义,同时满足 TimescaleDB hypertable 要求 * (分区列 time 必须包含在主键中) * - 不再需要额外的 @Index unique — 复合主键已保证唯一性 */ /** 交易所标识(binance / okx / bybit) */ @PrimaryColumn("text") exchange!: string; /** 交易对符号(如 BTCUSDT) */ @PrimaryColumn("text") symbol!: string; /** K 线周期(1m) */ @PrimaryColumn("text") interval!: KlineInterval; /** K 线开盘时间(UTC)— @timescaledb/typeorm 自动标记为时间分区列 */ @TimeColumn() @PrimaryColumn("timestamptz") time!: Date; // ============================================================ // 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; }