b4c7636731
- 配置层: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 方案文档
199 lines
5.9 KiB
Markdown
199 lines
5.9 KiB
Markdown
# 接入 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)
|