添加 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 方案文档
This commit is contained in:
@@ -144,6 +144,11 @@ export const exchange = {
|
||||
apiKey: rawConfig.exchange.binance.api_key,
|
||||
apiSecret: rawConfig.exchange.binance.api_secret,
|
||||
},
|
||||
/** USDT-M 永续合约 API Key */
|
||||
binanceFutures: {
|
||||
apiKey: rawConfig.exchange.binance_futures.api_key,
|
||||
apiSecret: rawConfig.exchange.binance_futures.api_secret,
|
||||
},
|
||||
} as const;
|
||||
|
||||
// ============================================================
|
||||
@@ -170,6 +175,10 @@ export function printConfigSummary(): void {
|
||||
apiKey: exchange.binance.apiKey.slice(0, 6) + "***",
|
||||
apiSecret: "***",
|
||||
},
|
||||
binanceFutures: {
|
||||
apiKey: exchange.binanceFutures.apiKey.slice(0, 6) + "***",
|
||||
apiSecret: "***",
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
level: logging.level,
|
||||
|
||||
@@ -34,7 +34,10 @@ export interface RedisConfig {
|
||||
|
||||
/** 交易所 API 密钥配置(按交易所 ID 索引) */
|
||||
export interface ExchangeConfig {
|
||||
/** 现货市场 API Key */
|
||||
binance: ExchangeApiKeys;
|
||||
/** USDT-M 永续合约 API Key */
|
||||
binance_futures: ExchangeApiKeys;
|
||||
// 未来扩展:okx、bybit 等
|
||||
[exchangeId: string]: ExchangeApiKeys | undefined;
|
||||
}
|
||||
@@ -105,6 +108,15 @@ export function validateConfig(raw: unknown): EnvConfig {
|
||||
const binanceApiKey = assertString(binanceObj["api_key"], "exchange.binance.api_key");
|
||||
const binanceApiSecret = assertString(binanceObj["api_secret"], "exchange.binance.api_secret");
|
||||
|
||||
// --- binance_futures ---
|
||||
const binanceFutures = exObj["binance_futures"];
|
||||
if (typeof binanceFutures !== "object" || binanceFutures === null) {
|
||||
throw new Error("[config] env.yaml exchange 缺少 binance_futures 配置");
|
||||
}
|
||||
const futuresObj = binanceFutures as Record<string, unknown>;
|
||||
const futuresApiKey = assertString(futuresObj["api_key"], "exchange.binance_futures.api_key");
|
||||
const futuresApiSecret = assertString(futuresObj["api_secret"], "exchange.binance_futures.api_secret");
|
||||
|
||||
// --- logging ---
|
||||
const logging = obj["logging"];
|
||||
if (typeof logging !== "object" || logging === null) {
|
||||
@@ -132,6 +144,10 @@ export function validateConfig(raw: unknown): EnvConfig {
|
||||
api_key: binanceApiKey,
|
||||
api_secret: binanceApiSecret,
|
||||
},
|
||||
binance_futures: {
|
||||
api_key: futuresApiKey,
|
||||
api_secret: futuresApiSecret,
|
||||
},
|
||||
},
|
||||
logging: {
|
||||
level: logLevel,
|
||||
|
||||
@@ -22,8 +22,6 @@ import {
|
||||
import { Exchange } from "./exchange.entity";
|
||||
import { CommonBaseEntity } from "./common.entity";
|
||||
|
||||
import type { KlineInterval } from '../../types';
|
||||
|
||||
@Entity("trading_pairs")
|
||||
@Index(["exchange", "symbol"], { unique: true }) // 同一交易所下 symbol 唯一
|
||||
@Index(["active"]) // 按激活状态快速筛选
|
||||
@@ -69,18 +67,6 @@ export class TradingPair extends CommonBaseEntity {
|
||||
@Column("boolean", { default: true })
|
||||
active!: boolean;
|
||||
|
||||
/** 是否启用 K 线合成(false 时仅采集原始行情,不合成) */
|
||||
@Column("boolean", { default: true })
|
||||
kline_synthesis_enabled!: boolean;
|
||||
|
||||
/** K 线时间周期 */
|
||||
@Column("varchar", { length: 100, default: "1m" })
|
||||
kline_interval!: KlineInterval;
|
||||
|
||||
/** K 线合成周期列表(逗号分隔,如 "1m,5m,15m,1h,4h,1d") */
|
||||
@Column("varchar", { length: 100, default: "1m,5m,15m,1h,4h,1d" })
|
||||
kline_intervals!: string;
|
||||
|
||||
/**
|
||||
* 历史 K 线最后补全时间(UTC)。
|
||||
* 记录最近一次 REST 补拉 K 线的结束时间戳,
|
||||
@@ -96,16 +82,4 @@ export class TradingPair extends CommonBaseEntity {
|
||||
/** 备注 */
|
||||
@Column("text", { nullable: true })
|
||||
notes?: string;
|
||||
|
||||
// ============================================================
|
||||
// 工具方法
|
||||
// ============================================================
|
||||
|
||||
/** 解析 kline_intervals 为周期数组 */
|
||||
getIntervals(): string[] {
|
||||
return this.kline_intervals
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,15 +93,6 @@ CREATE TABLE IF NOT EXISTS trading_pairs (
|
||||
-- 是否激活数据订阅(false 时不采集该交易对行情)
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
-- 是否启用 K 线合成(false 时仅采集原始行情,不合成)
|
||||
kline_synthesis_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
-- 默认 K 线周期
|
||||
kline_interval VARCHAR(100) NOT NULL DEFAULT '1m',
|
||||
|
||||
-- K 线合成周期列表(逗号分隔,如 "1m,5m,15m,1h,4h,1d")
|
||||
kline_intervals VARCHAR(100) NOT NULL DEFAULT '1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,1d,1w,1mon',
|
||||
|
||||
-- 历史 K 线最后补全时间(UTC)。默认 Unix epoch 起始,
|
||||
-- 新交易对从 epoch 起始时间开始全量补拉。
|
||||
last_backfill_time TIMESTAMPTZ NOT NULL DEFAULT to_timestamp(0),
|
||||
@@ -321,7 +312,7 @@ ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
-- 默认交易对(仅 Binance 主流 USDT 永续合约,幂等)
|
||||
INSERT INTO trading_pairs (exchange_id, symbol, base_asset, quote_asset,
|
||||
price_precision, quantity_precision, kline_interval, kline_intervals, active)
|
||||
price_precision, quantity_precision, active)
|
||||
SELECT
|
||||
e.id,
|
||||
sym.symbol,
|
||||
@@ -329,8 +320,6 @@ SELECT
|
||||
sym.quote,
|
||||
2, -- price_precision(USDT 计价通常 2 位小数)
|
||||
5, -- quantity_precision(数量通常 5 位小数)
|
||||
'1m',
|
||||
'1m,5m,15m,30m,1h,4h,1d,1w',
|
||||
TRUE
|
||||
FROM exchanges e
|
||||
CROSS JOIN (
|
||||
|
||||
@@ -58,7 +58,6 @@ async function run(): Promise<void> {
|
||||
base_asset: seed.baseAsset,
|
||||
quote_asset: seed.quoteAsset,
|
||||
active: true,
|
||||
kline_synthesis_enabled: true,
|
||||
});
|
||||
|
||||
await pairRepo.save(pair);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// ============================================================
|
||||
|
||||
import { logger } from "../utils/logger";
|
||||
import type { Kline, KlineInterval, MarketInfo, RestClientConfig } from "../types";
|
||||
import type { Kline, MarketInfo, RestClientConfig } from "../types";
|
||||
import { DEFAULT_REST_CONFIG } from "../types/base";
|
||||
|
||||
// ============================================================
|
||||
@@ -76,7 +76,7 @@ export abstract class BaseRestClient {
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 拉取历史 K 线数据(REST)。
|
||||
* 拉取 1m 历史 K 线数据(REST)。
|
||||
*
|
||||
* 子类负责:
|
||||
* 1. 调用交易所原生 SDK 的 K 线接口
|
||||
@@ -84,14 +84,12 @@ export abstract class BaseRestClient {
|
||||
* 3. 处理分页逻辑(若时间跨度超过单次请求上限)
|
||||
*
|
||||
* @param symbol - 交易对符号(如 BTCUSDT)
|
||||
* @param interval - K 线周期
|
||||
* @param startTime - 起始时间(Unix ms)
|
||||
* @param endTime - 结束时间(Unix ms)
|
||||
* @param limit - 单次最大条数(默认取自 config.defaultLimit)
|
||||
*/
|
||||
abstract fetchKlines(
|
||||
symbol: string,
|
||||
interval: KlineInterval,
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
limit?: number,
|
||||
|
||||
+11
-24
@@ -1,4 +1,4 @@
|
||||
import { MainClient, type Kline as BinanceRestKline } from "binance";
|
||||
import { MainClient, type Kline as BinanceRestKline, CoinMClient } from "binance";
|
||||
|
||||
import { logger } from "../utils/logger";
|
||||
import { exchange } from "../config";
|
||||
@@ -48,7 +48,6 @@ export const KLINE_INTERVAL_MS: Record<KlineInterval, number> = {
|
||||
function convertBinanceKline(
|
||||
raw: BinanceRestKline,
|
||||
symbol: string,
|
||||
interval: KlineInterval,
|
||||
): Kline {
|
||||
const [
|
||||
openTime,
|
||||
@@ -68,7 +67,7 @@ function convertBinanceKline(
|
||||
return {
|
||||
exchange: "binance",
|
||||
symbol,
|
||||
interval,
|
||||
interval: "1m",
|
||||
openTime: openTime,
|
||||
closeTime: closeTime,
|
||||
open: String(open),
|
||||
@@ -89,20 +88,18 @@ function convertBinanceKline(
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 通过 Binance 原生 SDK 拉取 UI K 线并转换为本系统 Kline。
|
||||
* 通过 Binance 原生 SDK 拉取 1m K 线并转换为本系统 Kline。
|
||||
*
|
||||
* getUIKlines 与 getKlines 返回同构的 Kline[] 元组,
|
||||
* getUIKlines 额外支持 timeZone 参数,适合按交易所时区对齐。
|
||||
*
|
||||
* @param symbol - 交易对(如 BTCUSDT)
|
||||
* @param interval - K 线周期
|
||||
* @param startTime - 起始时间(Unix ms)
|
||||
* @param endTime - 结束时间(Unix ms),可选
|
||||
* @param limit - 单次拉取条数,默认 500(最大 1000)
|
||||
*/
|
||||
async function fetchBinanceKlines(
|
||||
symbol: string,
|
||||
interval: KlineInterval,
|
||||
startTime: number,
|
||||
endTime?: number,
|
||||
limit = 500,
|
||||
@@ -119,15 +116,15 @@ async function fetchBinanceKlines(
|
||||
|
||||
const rawKlines = await client.getKlines({
|
||||
symbol,
|
||||
interval,
|
||||
interval: "1m",
|
||||
startTime,
|
||||
endTime,
|
||||
limit: safeLimit,
|
||||
});
|
||||
|
||||
logger.info({
|
||||
logger.debug({
|
||||
symbol,
|
||||
interval,
|
||||
interval: "1m",
|
||||
startTime,
|
||||
endTime,
|
||||
limit: safeLimit,
|
||||
@@ -138,13 +135,7 @@ async function fetchBinanceKlines(
|
||||
}
|
||||
|
||||
return filterConsecutive(
|
||||
rawKlines.map((k, index) => {
|
||||
// if (index === rawKlines.length - 1) {
|
||||
// console.log(k);
|
||||
// }
|
||||
return convertBinanceKline(k, symbol, interval);
|
||||
}),
|
||||
interval,
|
||||
rawKlines.map((k) => convertBinanceKline(k, symbol)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,10 +156,7 @@ async function fetchBinanceKlines(
|
||||
* @param interval - K 线周期,用于查表获取 intervalMs
|
||||
* @returns 从首条开始严格连续的最大前缀子序列;空数组无缺口时返回完整排序结果
|
||||
*/
|
||||
function filterConsecutive(klines: Kline[], interval: KlineInterval) {
|
||||
// 查表获取当前 K 线周期对应的毫秒数
|
||||
const intervalMs = KLINE_INTERVAL_MS[interval];
|
||||
|
||||
function filterConsecutive(klines: Kline[]) {
|
||||
// 防御性排序:Binance API 不保证返回顺序,升序排列确保时间单调
|
||||
const results = klines.sort((a: Kline, b: Kline) => {
|
||||
return a.openTime - b.openTime;
|
||||
@@ -222,19 +210,18 @@ export class Client extends BaseRestClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取历史 K 线数据,返回标准化 Kline 数组。
|
||||
* 拉取 1m 历史 K 线数据,返回标准化 Kline 数组。
|
||||
*
|
||||
* 根据交易所 ID 分发到各自的 SDK 拉取函数。
|
||||
* K 线周期固定为 1m,高周期通过 TimescaleDB 连续聚合视图生成。
|
||||
*
|
||||
* @param symbol - 交易对符号(如 BTCUSDT)
|
||||
* @param interval - K 线周期
|
||||
* @param startTime - 起始时间(Unix ms)
|
||||
* @param endTime - 结束时间(Unix ms),可选
|
||||
* @param limit - 最大返回条数,默认取自 config.defaultLimit
|
||||
*/
|
||||
async fetchKlines(
|
||||
symbol: string,
|
||||
interval: KlineInterval,
|
||||
startTime: number,
|
||||
limit?: number,
|
||||
endTime?: number,
|
||||
@@ -242,7 +229,7 @@ export class Client extends BaseRestClient {
|
||||
const effectiveLimit = limit ?? this.config.defaultLimit;
|
||||
switch (this.exchange) {
|
||||
case "binance":
|
||||
return fetchBinanceKlines(symbol, interval, startTime, endTime, effectiveLimit);
|
||||
return fetchBinanceKlines(symbol, startTime, endTime, effectiveLimit);
|
||||
// TODO: 新增交易所在此添加 case
|
||||
// case "okx":
|
||||
// return fetchOkxKlines(symbol, interval, startTime, endTime, effectiveLimit);
|
||||
|
||||
@@ -18,7 +18,6 @@ for (const pair of allPairs) {
|
||||
console.log('lastBackfillTime', lastBackfillTime);
|
||||
const klines = await client.fetchKlines(
|
||||
pair.symbol,
|
||||
pair.kline_interval,
|
||||
lastBackfillTime,
|
||||
500
|
||||
);
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
import { Client } from "../exchanges/rest";
|
||||
import type { Kline, KlineInterval } from "../types";
|
||||
import type { Kline } from "../types";
|
||||
|
||||
const client = new Client("binance");
|
||||
|
||||
/**
|
||||
* 获取 Binance K 线数据(基于 MainClient REST API)。
|
||||
* 获取 Binance 1m K 线数据(基于 MainClient REST API)。
|
||||
*
|
||||
* 内部复用 Client(多交易所 REST 客户端)的 binance 实现,
|
||||
* 包含限流、Binance SDK 原生转换、连续性过滤等逻辑。
|
||||
* 返回本系统标准化 {@link Kline} 数组。
|
||||
*
|
||||
* @param symbol - 交易对符号(如 "BTCUSDT")
|
||||
* @param interval - K 线周期(如 "1h"、"4h"、"1d")
|
||||
* @param startTime - 起始时间(Unix ms)
|
||||
* @param endTime - 结束时间(Unix ms),可选;不传则拉取到最新
|
||||
* @param limit - 单次拉取条数,默认 500(最大 1000)
|
||||
*/
|
||||
export async function fetchKlines(
|
||||
symbol: string,
|
||||
interval: KlineInterval,
|
||||
startTime: number,
|
||||
endTime?: number,
|
||||
limit = 500,
|
||||
): Promise<Kline[]> {
|
||||
// Client.fetchKlines 参数顺序:symbol, interval, startTime, limit, endTime
|
||||
return client.fetchKlines(symbol, interval, startTime, limit, endTime);
|
||||
return client.fetchKlines(symbol, startTime, limit);
|
||||
}
|
||||
|
||||
|
||||
console.log(await fetchKlines('BTCUSDT.P', 0, 10));
|
||||
@@ -234,7 +234,6 @@ export interface MarketDataFeed {
|
||||
* REST 拉取历史 K 线(用于补齐缺失数据或回测)。
|
||||
*
|
||||
* @param symbol - 交易对符号
|
||||
* @param interval - K 线周期
|
||||
* @param startTime - 起始时间(Unix ms)
|
||||
* @param endTime - 结束时间(Unix ms)
|
||||
* @param limit - 最大返回条数(默认 500)
|
||||
@@ -242,7 +241,6 @@ export interface MarketDataFeed {
|
||||
*/
|
||||
fetchKlines(
|
||||
symbol: string,
|
||||
interval: KlineInterval,
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
limit?: number,
|
||||
|
||||
Reference in New Issue
Block a user