Files
trade/data/schema/klines.sql
T
Rekey e91cad79e6 feat(data): 实现配置表 CRUD 与 Schema 初始化拆分
- 新增 data/db/ 数据库访问层:pool 管理、类型定义、Zod 校验、参数化 SQL 查询
- 新增 data/db/config-crud.ts:MonitoredSymbolsRepo / ExchangeConfigRepo / AppConfigRepo 三个 CRUD 服务类
- 新增 data/config.ts:中心化配置模块,零依赖 .env 解析 + Zod 校验
- 新增 data/schema/:klines.sql + config.sql 参考 DDL
- 新增 data/exchanges/:交易所类型定义与 Binance WebSocket 封装
- 新增 data/run/:交易所连接启动入口
- 重构 data/init-db/:001_init.sql 仅保留 TimescaleDB + klines,配置表拆分至 002_config.sql
- 更新 docker-compose.yml:挂载 init-db 初始化脚本
2026-06-07 20:46:35 +08:00

207 lines
8.2 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================
-- schema/klines.sql — K 线表 DDL(参考副本)
-- ============================================================
-- 数据库:TimescaleDB (PostgreSQL 17 + timescaledb 2.x)
-- 说明:存储全交易所 OHLCV 数据,按时间自动分区压缩
--
-- ⚠️ 权威初始化脚本:data/init-db/001_init.sql
-- 本文件保留作为 pg.initSchema() 非 Docker 部署的回退方案和文档参考。
-- 修改表结构时请同步更新 001_init.sql。
-- ============================================================
-- ============================================================
-- 1. klines — K 线主表(hypertable
-- ============================================================
CREATE TABLE IF NOT EXISTS klines (
-- ---- 时间维度 ----
time TIMESTAMPTZ NOT NULL, -- K 线开盘时间(UTC
-- ---- 标识维度 ----
exchange TEXT NOT NULL, -- 交易所:binance / okx / bybit
symbol TEXT NOT NULL, -- 交易对:BTCUSDT / ETHUSDT
interval TEXT NOT NULL, -- 周期:1m / 5m / 15m / 1h / 4h / 1d
-- ---- OHLCV 核心数据 ----
open NUMERIC(20,8) NOT NULL,
high NUMERIC(20,8) NOT NULL,
low NUMERIC(20,8) NOT NULL,
close NUMERIC(20,8) NOT NULL,
volume NUMERIC(20,8) NOT NULL DEFAULT 0, -- 成交量(基准币种)
-- ---- 扩展字段 ----
quote_volume NUMERIC(20,8) DEFAULT 0, -- 成交额(计价币种)
taker_buy_base_vol NUMERIC(20,8) DEFAULT 0, -- 主动买入量(基准币种)
taker_buy_quote_vol NUMERIC(20,8) DEFAULT 0, -- 主动买入额(计价币种)
trade_count INTEGER DEFAULT 0, -- 成交笔数
is_closed BOOLEAN DEFAULT TRUE, -- K 线是否已闭合
-- ---- 元数据 ----
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
-- 唯一约束:同一根 K 线不可重复
UNIQUE (time, exchange, symbol, interval)
);
-- ============================================================
-- 2. 转换为 TimescaleDB hypertable
-- ============================================================
-- 按 time 列做 1 天分区,按 exchange 做 4 空间分区
SELECT create_hypertable(
'klines',
'time',
chunk_time_interval => INTERVAL '1 day',
partitioning_column => 'exchange',
number_partitions => 4,
if_not_exists => TRUE
);
-- ============================================================
-- 3. 索引设计
-- ============================================================
-- 主力查询索引(覆盖 95% 查询场景)
-- 用途:按交易对+周期+时间范围查询最新 K 线
CREATE INDEX IF NOT EXISTS idx_klines_lookup
ON klines (exchange, symbol, interval, time DESC);
-- 回测专用索引
-- 用途:按交易对+周期+时间正序遍历(策略回测)
CREATE INDEX IF NOT EXISTS idx_klines_backtest
ON klines (symbol, interval, time ASC);
-- 最新已闭合 K 线索引(部分索引,减小体积)
-- 用途:获取已完成的最新 K 线(避免扫描未闭合数据)
CREATE INDEX IF NOT EXISTS idx_klines_latest
ON klines (exchange, symbol, interval, time DESC)
WHERE is_closed = TRUE;
-- ============================================================
-- 4. 压缩策略(列式压缩,压缩比约 90%)
-- ============================================================
ALTER TABLE klines SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'exchange, symbol, interval',
timescaledb.compress_orderby = 'time DESC'
);
-- K 线闭合 7 天后自动触发压缩
SELECT add_compression_policy('klines', INTERVAL '7 days', if_not_exists => TRUE);
-- ============================================================
-- 5. 数据保留策略
-- ============================================================
-- 1m 粒度 K 线保留 90 天(更粗粒度由连续聚合视图覆盖)
SELECT add_retention_policy('klines', INTERVAL '90 days', if_not_exists => TRUE);
-- ============================================================
-- 6. 连续聚合视图(从 1m 自动派生高周期 K 线)
-- ============================================================
-- ---- 5m K 线 ----
CREATE MATERIALIZED VIEW IF NOT EXISTS klines_5m
WITH (timescaledb.continuous) AS
SELECT
time_bucket('5 minutes', time) AS time,
exchange,
symbol,
'5m'::TEXT AS interval,
FIRST(open, time) AS open,
MAX(high) AS high,
MIN(low) AS low,
LAST(close, time) AS close,
SUM(volume) AS volume,
SUM(quote_volume) AS quote_volume,
SUM(taker_buy_base_vol) AS taker_buy_base_vol,
SUM(taker_buy_quote_vol) AS taker_buy_quote_vol,
SUM(trade_count) AS trade_count
FROM klines
WHERE interval = '1m'
GROUP BY time_bucket('5 minutes', time), exchange, symbol;
-- ---- 15m K 线 ----
CREATE MATERIALIZED VIEW IF NOT EXISTS klines_15m
WITH (timescaledb.continuous) AS
SELECT
time_bucket('15 minutes', time) AS time,
exchange,
symbol,
'15m'::TEXT AS interval,
FIRST(open, time) AS open,
MAX(high) AS high,
MIN(low) AS low,
LAST(close, time) AS close,
SUM(volume) AS volume,
SUM(quote_volume) AS quote_volume,
SUM(taker_buy_base_vol) AS taker_buy_base_vol,
SUM(taker_buy_quote_vol) AS taker_buy_quote_vol,
SUM(trade_count) AS trade_count
FROM klines
WHERE interval = '1m'
GROUP BY time_bucket('15 minutes', time), exchange, symbol;
-- ---- 1h K 线 ----
CREATE MATERIALIZED VIEW IF NOT EXISTS klines_1h
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS time,
exchange,
symbol,
'1h'::TEXT AS interval,
FIRST(open, time) AS open,
MAX(high) AS high,
MIN(low) AS low,
LAST(close, time) AS close,
SUM(volume) AS volume,
SUM(quote_volume) AS quote_volume,
SUM(taker_buy_base_vol) AS taker_buy_base_vol,
SUM(taker_buy_quote_vol) AS taker_buy_quote_vol,
SUM(trade_count) AS trade_count
FROM klines
WHERE interval = '1m'
GROUP BY time_bucket('1 hour', time), exchange, symbol;
-- ---- 1d K 线 ----
CREATE MATERIALIZED VIEW IF NOT EXISTS klines_1d
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 day', time) AS time,
exchange,
symbol,
'1d'::TEXT AS interval,
FIRST(open, time) AS open,
MAX(high) AS high,
MIN(low) AS low,
LAST(close, time) AS close,
SUM(volume) AS volume,
SUM(quote_volume) AS quote_volume,
SUM(taker_buy_base_vol) AS taker_buy_base_vol,
SUM(taker_buy_quote_vol) AS taker_buy_quote_vol,
SUM(trade_count) AS trade_count
FROM klines
WHERE interval = '1m'
GROUP BY time_bucket('1 day', time), exchange, symbol;
-- 连续聚合视图也启用压缩
ALTER MATERIALIZED VIEW klines_5m SET (timescaledb.compress = true);
ALTER MATERIALIZED VIEW klines_15m SET (timescaledb.compress = true);
ALTER MATERIALIZED VIEW klines_1h SET (timescaledb.compress = true);
ALTER MATERIALIZED VIEW klines_1d SET (timescaledb.compress = true);
-- ============================================================
-- 7. 常用查询示例
-- ============================================================
-- 查询最新 N 根 1h K 线
-- SELECT time, open, high, low, close, volume
-- FROM klines_1h
-- WHERE exchange = 'binance' AND symbol = 'BTCUSDT'
-- ORDER BY time DESC LIMIT 100;
-- 查询某个时间范围内的原始 1m K 线
-- SELECT time, open, high, low, close, volume
-- FROM klines
-- WHERE exchange = 'binance' AND symbol = 'ETHUSDT' AND interval = '1m'
-- AND time BETWEEN '2026-06-01' AND '2026-06-06'
-- ORDER BY time ASC;