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

199 lines
5.9 KiB
Markdown
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.
# 接入 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`**
```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`**
```typescript
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()`
```typescript
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));
}
```
`Client``fetchKlines()` 增加分支:
```typescript
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`**
种子数据新增合约交易对:
```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