Files
trade/PLAN-add-futures-data.md
T
Rekey b4c7636731 添加 USDT-M 合约数据支持(配置层 + 清理多余字段)
- 配置层:env.yaml 新增 binance_futures API Key 段,validators + config 同步
- 清理 TradingPair 实体:删除 kline_interval、kline_intervals、kline_synthesis_enabled
- 删除 fetchKlines 系列函数的 interval 参数,硬编码为 1m
- 更新 SQL seed 数据、example、base_rest 接口、types 接口
- 新增 AGENTS/08-boundaries.md 执行纪律
- 新增 PLAN-add-futures-data.md 方案文档
2026-06-15 23:24:21 +08:00

5.9 KiB
Raw Blame History

接入 USDT-M 合约数据 — 改造方案

设计思路

用 symbol 后缀区分账户类型,而非新增 account_type 列。核心原则是尽量不动已有表结构和聚合视图。

Symbol 命名约定

账户类型 symbol 示例 说明
现货 BTCUSDT 不变
USDT-M 永续 BTCUSDT.P .P 后缀标记合约
Coin-M 永续 BTCUSDT_PERP 预留

核心机制:对外(入库、查询、展示)统一用带后缀的 symbol;对内(调用 Binance SDK)自动 strip 后缀。


改动清单

1. 配置层(3 文件)

data/env.yaml

exchange:
  binance:                     ← 保留不动(向后兼容)
    api_key: "..."
    api_secret: "..."
  binance_futures:             ← 新增
    api_key: "..."
    api_secret: "..."

data/config/validators.ts

  • ExchangeConfig 接口中 binance: ExchangeApiKeys 保持不动
  • 新增 binance_futures: ExchangeApiKeys
  • validateConfig() 中新增解析 exchange.binance_futures

data/config/index.ts

  • 导出 exchange.binance(已有)+ 新增 exchange.binanceFutures

2. REST 客户端(1 文件,核心改动)

data/exchanges/rest.ts

import { USDMClient } from "binance";         // 新增引入

// 工具函数:提取裸 symbol(移除 .P / _PERP 后缀)
function stripSuffix(symbol: string): string {
    return symbol.replace(/\.(P|PERP)$/, "");
}

// 判断是否为合约 symbol
function isFuturesSymbol(symbol: string): boolean {
    return symbol.endsWith(".P") || symbol.endsWith("_PERP");
}

现有 fetchBinanceKlines() 保持不变(处理现货)。新增 fetchFuturesKlines()

async function fetchFuturesKlines(
    symbol: string,           // BTCUSDT.P
    interval: KlineInterval,
    startTime: number,
    endTime?: number,
    limit = 500,
): Promise<Kline[]> {
    const rawSymbol = stripSuffix(symbol);     // BTCUSDT
    const client = new USDMClient({
        api_key: exchange.binanceFutures.apiKey,
        api_secret: exchange.binanceFutures.apiSecret,
    }, { timeout: 3000 });

    const rawKlines = await client.getKlines({
        symbol: rawSymbol,
        interval,
        startTime,
        endTime,
        limit: Math.min(limit, 1000),
    });

    return rawKlines.map(k => convertBinanceKline(k, symbol, interval));
}

ClientfetchKlines() 增加分支:

async fetchKlines(...): Promise<Kline[]> {
    switch (this.exchange) {
        case "binance":
            if (isFuturesSymbol(symbol)) {
                return fetchFuturesKlines(symbol, interval, startTime, endTime, effectiveLimit);
            }
            return fetchBinanceKlines(symbol, interval, startTime, endTime, effectiveLimit);
    }
}

关键convertBinanceKline() 传入原始 "BTCUSDT.P",转换结果中 kline.symbol 自然就是 "BTCUSDT.P",入库后与现货 "BTCUSDT" 不会 PK 冲突。


3. 服务层(1 文件)

data/service/kline.ts

  • 不需任何改动。upsertOrUpdateKlines() 直接把 Kline.symbol 写入 KlineEntity.symbol,后缀天然带进去。

4. 实体层(0 文件)

结论:不需要改。

  • kline.entity.ts 的 4 列 PK 保持不变
  • trading-pair.entity.ts 不需要加 account_type,用 symbol 本身的 .P 后缀标识即可

TradingPair 的 symbol 设成 "BTCUSDT.P",唯一约束 (exchange_id, symbol) 自动不冲突。


5. SQL 初始化脚本(1 文件)

data/db/init-db/02-init-tables.sql

种子数据新增合约交易对:

INSERT INTO trading_pairs (exchange_id, symbol, base_asset, quote_asset,
    price_precision, quantity_precision, kline_interval, kline_intervals, active)
SELECT
    e.id,
    sym.symbol,
    sym.base,
    sym.quote,
    2, 5, '1m', '1m,5m,15m,30m,1h,4h,1d,1w', TRUE
FROM exchanges e
CROSS JOIN (
    VALUES
        ('BTCUSDT.P',  'BTC',  'USDT'),
        ('ETHUSDT.P',  'ETH',  'USDT')
) AS sym(symbol, base, quote)
WHERE e.name = 'binance'
ON CONFLICT (exchange_id, symbol) DO NOTHING;

klines 表结构和连续聚合视图:完全不动。 symbol 值不同,数据天然隔离。


6. 运行脚本(1 文件)

data/run/exchange.ts

  • getAllPairs() 不受影响,返回的 symbol 本身就是 "BTCUSDT.P"
  • new Client("binance")fetchKlines() 内部已按 symbol 后缀分派到 USDMClient
  • 回补循环逻辑不变,lastBackfillTime 追踪机制不变

注意Binance 的 USDMClient.getKlines() 返回与 MainClient.getKlines() 同构的 12 元组,convertBinanceKline() 可以直接复用。


7. 类型定义(0 文件)

Kline.symbol 字段类型已是 string,直接存 "BTCUSDT.P"。无需改动。


改动汇总

文件 改动类型 行数估算
data/env.yaml 新增 binance_futures +4
data/config/validators.ts ExchangeConfig + validateConfig() 新增解析 +10
data/config/index.ts 新增 exchange.binanceFutures 导出 +5
data/exchanges/rest.ts 引入 USDMClient,新增 fetchFuturesKlines()Client.fetchKlines() 增加分支 +40
data/db/init-db/02-init-tables.sql seed 数据插入合约交易对 +15
data/run/exchange.ts 无实质改动(后缀自动路由) 0

不动:klines 表结构、复合主键、连续聚合视图、service/kline.ts、types 目录、实体层。

5-6 个文件,~70 行净改动


工作顺序

  1. env.yaml — 配好 futures 的 API Key
  2. config/validators.ts + config/index.ts — 解析 + 导出 futures Key
  3. exchanges/rest.ts — 核心改动,USDMClient + 后缀分派
  4. 02-init-tables.sql — 种子数据
  5. 验证:跑 bun run data/run/exchange.ts 看能否拉下 BTCUSDT.P 的 K 线
  6. Coin-M 同理扩展(只加 suffix 规则 + CoinMClient case