Files
trade/data/config/index.ts
T
Rekey 9351dec226 refactor: migrate API keys to config, extend Kline intervals, add DB extensions
Security:
- Move hardcoded Binance API key/secret from rest.ts to env.yaml (exchange config segment)
- Add ExchangeConfig validation in config/validators.ts
- Export typed exchange config from config/index.ts
- Update AGENTS/07-caveats.md to reflect the new policy

Kline intervals (add 3m / 2h / 6h / 8h / 1mon):
- TypeScript: update KlineInterval type, KLINE_INTERVAL_MS mapping, build_aggregates_sql refresh chain
- Python: sync KlineInterval Literal type, INTERVAL_TO_TABLE and INTERVAL_MS mappings, db_test table list
- SQL: add 5 continuous aggregate materialized views (klines_3m/2h/6h/8h/1mon) with indexes
- SQL: extend default kline_intervals in trading_pairs table
- SQL: add cross-sectional query indexes for klines_1d and klines_1w

DB:
- Enable pg_prewarm extension (backtest warmup)
- Enable pg_stat_statements extension (slow query monitoring)

Other:
- data/run/exchange.ts: graceful pgsql shutdown after backfill completes
- Config path: load from data/env.yaml (symlink) instead of project root
2026-06-14 18:45:01 +08:00

182 lines
6.0 KiB
TypeScript
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.
// ============================================================
// config.ts — 中心化配置模块(YAML 驱动)
// ============================================================
// 职责:
// 1. 从项目根目录 env.yaml 加载配置
// 2. 使用 validateConfig() 校验并类型收窄
// 3. 导出按职责分组的强类型配置对象(pgsql / redis / logging
//
// 使用方式:
// import { pgsql, redis, logging } from "./config";
// const ds = new DataSource({ ...pgsql });
// const redisClient = new Redis(redis.url);
//
// 配置文件位置:data/env.yaml(软链接 → 项目根目录 env.yaml)
// TypeScript / Python 模块共享同一份配置。
// ============================================================
import { readFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parse as parseYaml } from "yaml";
import { validateConfig, type EnvConfig } from "./validators";
// ============================================================
// 1. 定位并读取 env.yaml
// ============================================================
/**
* 计算项目根目录的绝对路径。
* data/config.ts → data/ → <project_root>/
*
* 兼容 ESM(无 __dirname)和 Bun 运行时。
*/
function getProjectRoot(): string {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// config/index.ts → config/ → data/ → <project_root>
return resolve(__dirname, "../..");
}
/**
* 读取 data/env.yaml(软链接指向项目根目录 env.yaml)并解析为原始对象。
* 文件不存在时抛出明确错误,不做静默降级。
*/
function loadYamlConfig(): unknown {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// config/index.ts → config/ → data/env.yaml
const yamlPath = resolve(__dirname, "../env.yaml");
let content: string;
try {
content = readFileSync(yamlPath, "utf-8");
} catch {
throw new Error(
`[config] 无法读取配置文件: ${yamlPath}\n` +
`请确保 data/env.yaml 软链接指向项目根目录的 env.yaml。`,
);
}
const parsed = parseYaml(content);
if (parsed === null || parsed === undefined) {
throw new Error(`[config] env.yaml 解析结果为空: ${yamlPath}`);
}
return parsed;
}
// ============================================================
// 2. 加载 & 校验
// ============================================================
/** 经校验后的 env.yaml 配置(强类型) */
const rawConfig: EnvConfig = (() => {
const raw = loadYamlConfig();
return validateConfig(raw);
})();
// ============================================================
// 3. 按职责分组的导出配置对象
// ============================================================
/**
* PostgreSQL / TimescaleDB 连接配置
*
* 连接池参数(max / idleTimeoutMillis / connectionTimeoutMillis
* 为硬编码常量,不在 env.yaml 中暴露,避免误调导致连接耗尽。
*/
export const pgsql = {
host: rawConfig.db.host,
port: rawConfig.db.port,
database: rawConfig.db.name,
user: rawConfig.db.user,
password: rawConfig.db.password,
/** pg.Pool 连接上限,避免连接数暴涨 */
max: 20,
/** 连接空闲超时(毫秒) */
idleTimeoutMillis: 30000,
/** 连接获取超时(毫秒) */
connectionTimeoutMillis: 5000,
} as const;
/**
* Redis 连接与发布配置
*
* channelPrefix / retryDelayBaseMs / maxRetries 为硬编码常量,
* 跨模块保持一致,不需要通过配置文件修改。
*/
export const redis = {
/** Redis 连接 URLioredis 可直接使用) */
url: rawConfig.redis.url,
/** 是否启用 Pub/Sub 发布行情数据(开发环境可关闭以节省资源) */
publishEnabled: rawConfig.redis.publish_enabled,
/** 频道前缀,避免多环境 key 冲突 */
channelPrefix: "trade",
/** 重连策略:指数退避基数(毫秒) */
retryDelayBaseMs: 1000,
/** 最大重试次数 */
maxRetries: 10,
} as const;
/**
* 日志配置
*
* pretty 由 NODE_ENV 自动推导,不在 env.yaml 中独立配置。
*/
export const logging = {
/** 日志级别:trace / debug / info / warn / error / fatal */
level: rawConfig.logging.level,
/** 运行环境(production 时 pino 输出 JSON 便于日志采集) */
nodeEnv: rawConfig.logging.node_env,
/** 是否启用 pino-pretty(开发环境友好输出) */
pretty: rawConfig.logging.node_env === "development",
} as const;
/**
* 交易所 API 密钥配置
*
* 按交易所 ID 索引,目前仅 binance。
* 新增交易所时在 env.yaml 的 exchange 段添加对应子配置即可。
*/
export const exchange = {
binance: {
apiKey: rawConfig.exchange.binance.api_key,
apiSecret: rawConfig.exchange.binance.api_secret,
},
} as const;
// ============================================================
// 4. 工具:运行时打印配置概要(不含敏感信息)
// ============================================================
/** 打印脱敏后的配置概要,便于启动排查 */
export function printConfigSummary(): void {
const summary = {
projectRoot: getProjectRoot(),
pgsql: {
host: pgsql.host,
port: pgsql.port,
database: pgsql.database,
user: pgsql.user,
password: "***",
},
redis: {
url: redis.url.replace(/\/\/.*@/, "//***@"), // 隐藏密码
publishEnabled: redis.publishEnabled,
},
exchange: {
binance: {
apiKey: exchange.binance.apiKey.slice(0, 6) + "***",
apiSecret: "***",
},
},
logging: {
level: logging.level,
nodeEnv: logging.nodeEnv,
pretty: logging.pretty,
},
};
console.log("[config] 配置概要:", JSON.stringify(summary, null, 2));
}