- comparison_2h_6h: 9 策略 × 4 币种 × 2 周期 × 4 数据量 = 288 次回测
- 包含海龟、超级趋势、MACD、布林收缩、三均线、RSI 回归、
ATR 波动率突破、EMA 多空、牛熊自适应
- 结论:6h 夏普显著优于 2h(69% 组合),ATR 策略霸榜
- 自动生成 Markdown 回测报告
- vol_break_1h_6h: ATR 波动率突破 × 1h/2h/4h/6h 近半年对比
数字货币量化交易系统开发计划
基于 Python + TypeScript 混合架构的数字货币量化交易系统 —— 核心模块划分与开发步骤
目录
项目概述
本项目的目标是构建一套可扩展、低延迟、稳定可靠的数字货币量化交易系统,支持:
- 多交易所接入(Binance、OKX、Bybit 等)
- 多策略并行运行
- 实时行情数据采集与存储
- 策略回测与参数优化
- 实盘交易执行与风控管理
- 可视化监控与告警
架构策略
采用 Python + TypeScript 混合架构:
| 层 | 语言 | 职责 |
|---|---|---|
| 数据层 | TypeScript (Bun) | 行情采集、WebSocket 连接管理、K 线合成、数据写入 |
| 业务层 | Python 3.10+ | 策略引擎、回测、风控、交易执行 |
| 接口层 | TypeScript / Python | FastAPI (Python) 或 NestJS (TS) 提供 REST/WS API |
技术栈选型
| 分类 | 技术 / 库 | 说明 |
|---|---|---|
| 数据层语言 | TypeScript 5.x (Bun) | 行情采集、WebSocket 管理、数据管道 |
| 业务层语言 | Python 3.10+ | 策略引擎、回测、风控逻辑 |
| 时序数据库 | TimescaleDB (推荐) | K 线数据存储,基于 PostgreSQL 扩展 |
| 关系型数据库 | PostgreSQL 16+ | 订单、策略配置、用户数据等(TimescaleDB 基于 PG,可共用) |
| 消息队列 | Redis / NATS | 跨语言数据流解耦,事件驱动 |
| 异步框架(TS) | ws + axios + bull |
WebSocket 客户端、HTTP 请求、任务队列 |
| 异步框架(Py) | asyncio + aiohttp / httpx |
异步 I/O,高性能网络请求 |
| 数据分析 | pandas + numpy + ta / talib |
技术指标计算与数据分析 |
| 策略回测 | backtrader / vectorbt / 自研 |
高性能回测引擎 |
| Web 框架 | FastAPI + Uvicorn | REST API 与 WebSocket 服务 |
| 可视化 | Grafana + Streamlit | 监控仪表盘与交互式分析 |
| 任务调度 | Celery / APScheduler / Bull | 定时任务与异步任务队列 |
| 配置管理 | pydantic-settings + .env |
类型安全的配置管理 |
| 日志监控 | loguru + winston + sentry | 结构化日志与异常追踪 |
| 类型校验 | Zod (TS) / Pydantic (Py) | 运行时数据校验,双端类型一致性保证 |
架构方案对比
方案 A:纯 Python(原方案)
[Python] 数据采集 → 策略引擎 → 交易执行 → 风控
| 优势 | 劣势 |
|---|---|
| 单一语言,维护简单 | WebSocket 大规模连接管理不如 Node.js |
| 数据分析生态无敌 | Python GIL 在 CPU 密集型场景受限 |
| 量化金融社区成熟 | 异步生态相对 Node.js 较新 |
方案 B:TypeScript 数据模块 + Python 业务引擎 ✅ (推荐)
[TypeScript] 数据采集 → Redis/消息队列 → [Python] 策略引擎 → 交易执行
| 优势 | 劣势 |
|---|---|
| Node.js WebSocket 性能优异,天然适合高并发连接 | 需要维护两套技术栈 |
| TypeScript 类型系统在数据管道中减少运行时错误 | 跨语言调试稍复杂 |
| 共享类型:前后端可用同一套 TypeScript 类型定义 | 部署需要 Bun + Python 双运行时 |
| 事件驱动模型与行情数据流天然匹配 | 团队需要双语言能力 |
| npm 生态有成熟的交易 SDK(ccxt 等) |
方案 C:全 TypeScript
[TypeScript] 数据采集 → 策略引擎 → 交易执行 → 风控
| 优势 | 劣势 |
|---|---|
| 单一语言 | 回测/量化分析生态远不如 Python |
| 类型安全 | 缺乏 pandas/numpy 级别的数据处理库 |
| 全栈一致性(前后端 + 数据层共用 TS) | 量化社区资源少,需要自研大量基础设施 |
核心模块划分
系统划分为 7 大核心模块,模块间通过消息队列或 API 解耦,整体架构如下:
┌─────────────────────────────────────────────────────────────┐
│ Web UI / Dashboard │
│ (FastAPI / Streamlit / Grafana) │
└──────────────┬──────────────────┬───────────────────────────┘
│ │
┌──────────▼──────────┐ ┌───▼────────────────────┐
│ API Gateway │ │ 监控告警模块 │
│ (REST + WebSocket) │ │ (Prometheus + Alert) │
└──────────┬──────────┘ └────────────────────────┘
│
┌──────────▼──────────────────────────────────────────┐
│ 策略引擎 🐍 Python │
│ (策略加载 / 生命周期管理 / 信号分发) │
└────┬──────────────┬──────────────┬─────────────────┘
│ │ │
┌────▼────┐ ┌─────▼─────┐ ┌───▼──────────┐
│ 策略 A │ │ 策略 B │ │ 策略 C ... │
│(趋势) │ │ (网格) │ │ │
└────┬────┘ └─────┬─────┘ └───┬──────────┘
│ │ │
┌────▼──────────────▼──────────────▼─────────────────┐
│ 交易执行模块 🐍 Python │
│ (订单管理 / 仓位管理 / 交易所适配器 / 重试机制) │
└────────────────────┬───────────────────────────────┘
│
┌────────────────────▼───────────────────────────────┐
│ 消息队列 (Redis / NATS) │
│ ┌───────────── 数据管道 ─────────────┐ │
└────────────────────┬───────────────────────────────┘
│
┌────────────────────▼───────────────────────────────┐
│ 数据模块 🟦 TypeScript │
│ (行情采集 / K线合成 / 数据存储 / 数据清洗 / 因子计算) │
└────────────────────┬───────────────────────────────┘
│
┌────────────────────▼───────────────────────────────┐
│ 风控模块 🐍 Python │
│ (资金管理 / 仓位限制 / 最大回撤 / 异常交易熔断 / 白名单)│
└─────────────────────────────────────────────────────┘
1. 数据模块 (data/) — 🟦 TypeScript
负责从交易所获取和处理市场数据,是一切策略和决策的基础。
为什么数据模块用 TypeScript?
| 原因 | 说明 |
|---|---|
| WebSocket 性能 | Node.js 事件循环天然适合处理大量 WebSocket 连接(单进程万级连接) |
| 内存管理 | V8 引擎的垃圾回收机制在短生命周期对象场景表现优异 |
| 类型安全 | TypeScript 编译期检查确保数据管道中的类型正确性 |
| 流式处理 | Node.js Stream / Observable 模式与行情数据流完美匹配 |
| 生态优势 | 各交易所官方/社区 SDK 对 Node.js 支持最好 |
子模块划分
| 子模块 | 职责 | 关键技术点 |
|---|---|---|
| 行情采集器 | 通过 WebSocket 订阅实时行情(ticker、orderbook、trade),定时拉取K线 | ws、断线重连、心跳检测、连接池管理 |
| K 线合成器 | 将实时 tick/trade 流合成为 OHLCV K 线,支持多周期并行、补齐、异常处理 | 时间桶算法、增量更新、多级合成(1m→5m→15m→1h) |
| 数据存储 | K 线数据持久化到 TimescaleDB,订单数据存入 PostgreSQL | TimescaleDB hypertable、批量 UPSERT、列式压缩 |
| 数据清洗 | 去除异常 tick、填充缺失值、处理停盘数据 | 统计异常检测、插值法 |
| 数据发布 | 将处理后的数据通过消息队列推送给 Python 策略引擎 | Redis Pub/Sub、NATS、协议缓冲(Protocol Buffers) |
关键接口
// src/data/collector.ts
export interface DataFeed {
subscribeTicker(symbols: string[]): Promise<void>;
subscribeOrderbook(symbol: string, depth: number): Promise<void>;
fetchKlines(symbol: string, interval: KlineInterval, limit?: number): Promise<Kline[]>;
}
// src/data/types.ts
export interface Ticker {
exchange: string;
symbol: string;
price: number;
volume: number;
timestamp: number;
}
export interface Kline {
exchange: string;
symbol: string;
interval: KlineInterval;
open: number;
high: number;
low: number;
close: number;
volume: number;
timestamp: number;
}
// src/data/pipeline.ts
export class DataPipeline {
constructor(
private collector: DataFeed,
private klineSynthesizer: KlineSynthesizer,
private storage: DataStorage,
private publisher: DataPublisher,
) {}
async start(): Promise<void> {
// 启动数据流管道
const tickerStream = await this.collector.subscribeTicker(['BTCUSDT', 'ETHUSDT']);
tickerStream
.pipe(this.klineSynthesizer.transform())
.pipe(this.storage.writeStream())
.pipe(this.publisher.publishStream());
}
}
K 线合成器详解
K 线合成器是数据模块中最核心的组件之一,负责将交易所推送的 实时离散数据 合成为标准的 OHLCV K 线。以下是其完整的工作机制。
1. 输入数据源
合成器接收三种类型的原始数据:
| 数据源 | 交易所推送频率 | 数据量 | 用途 |
|---|---|---|---|
| Ticker(最新成交价) | 100ms~1s/次 | 中等 | 实时价格监控,快速 K 线更新 |
| Trade(逐笔成交) | 毫秒级 | 极大 | 精确 K 线合成(推荐) |
| K 线(原始) | 1s~1min(交易所合成好的) | 小 | 直接使用,无需合成(最快方案) |
推荐方案:使用
Trade(逐笔成交)作为主数据源合成 K 线,精度最高;Ticker作为辅助用于快速预览。
2. 核心算法:时间桶(Time Bucket)
时间轴(1分钟 K 线为例):
时间桶 [t0, t0+1min)
┌─────────────────────────────────────────┐
│ Trade A Trade B Trade C │
│ p: 50000 p: 50020 p: 50010 │
│ v: 0.5 v: 1.2 v: 0.8 │
│ t: 00:10 t: 00:25 t: 00:45 │
│ │
│ OHLCV = { O: 50000, H: 50020, │
│ L: 50000, C: 50010, │
│ V: 2.5 } │
└─────────────────────────────────────────┘
算法步骤:
每收到一条 Trade/Ticker:
1. 计算时间桶索引:bucketIndex = floor(timestamp / interval_ms)
2. 如果 bucketIndex != currentBucketIndex:
a. 关闭当前桶,emit K 线
b. 创建新桶,初始化 O=H=L=C=当前价格, V=当前成交量
3. 否则(仍在当前桶内):
a. O = 不变(桶内第一条的价格)
b. H = max(H, 当前价格)
c. L = min(L, 当前价格)
d. C = 当前价格
e. V += 当前成交量
3. 多周期并行合成
系统需要同时维护多个周期的 K 线(1m、5m、15m、1h...),有两种实现策略:
策略 A:独立合成(每个周期独立计算) ✅ 推荐
Trade 流 ──► 1m 合成器 ──► 1m K 线
├──► 5m 合成器 ──► 5m K 线
├──► 15m 合成器 ─► 15m K 线
└──► 1h 合成器 ──► 1h K 线
- 每个周期维护独立的时间桶
- 优点:实现简单,各周期互不影响
- 缺点:内存占用随周期数量线性增长
策略 B:多级合成(从低周期聚合为高周期)
Trade 流 ──► 1m 合成器 ──► 5m 合成器 ──► 15m 合成器 ──► 1h 合成器
↑ 由 5 根 1m 合成 ↑ 由 3 根 5m 合成
- 高周期 K 线由低周期 K 线聚合而成
- 优点:内存效率高,无需为每个周期维护独立的 Trade 缓存
- 缺点:低周期 K 线未完成时,高周期无法产生完整 K 线
混合策略(推荐):
Trade 流 ──► 1m 合成器(实时,数据源为 Trade)
│
▼
1m K 线 ──► 5m 聚合器(由 5 根 1m 聚合)
│
▼
5m K 线 ──► 15m 聚合器
│
▼
15m K 线 ──► 1h 聚合器
1m 用 Trade 实时合成(精度最高),5m 及以上从 1m 聚合(计算量最小)。
4. K 线补齐与边界处理
这是合成器最容易被忽视但极其重要的部分:
| 场景 | 问题 | 处理方案 |
|---|---|---|
| 无成交周期 | 某个 1m 区间内没有任何 Trade | 使用前一根 K 线的 Close 作为当前 K 线的 OHLC(平盘),Volume=0 |
| 交易所暂停 | 交易所临时停盘或维护 | 停止 emit 新 K 线,标记为 gap: true,策略端跳过 |
| 周期边界错位 | 交易所 1h K 线从整点开始,本地合成可能有时区偏移 | 使用 UTC 时间统一对齐,bucketIndex = floor(utcTimestamp / intervalMs) * intervalMs |
| 首根 K 线不完整 | 系统启动时间不在周期起点,第一根 K 线数据不足 | emit 时标记 isPartial: true,策略端可选择忽略或使用 |
| 数据延迟/乱序 | Trade 到达顺序与发生顺序不一致 | 缓冲区保留 100ms 的排序窗口,按 timestamp 排序后处理 |
5. 增量更新 vs 完整替换
// 增量更新模式(推荐)—— 只推送变化部分,大幅减少数据量
interface KlineDelta {
exchange: string;
symbol: string;
interval: KlineInterval;
bucketTime: number; // 时间桶起始时间戳(毫秒)
o: number; // Open(只在开桶时更新)
h: number; // High
l: number; // Low
c: number; // Close
v: number; // Volume
isClosed: boolean; // 当前桶是否已关闭(不再变化)
tradeCount: number; // 桶内的成交笔数
}
// 完整 K 线(用于回测和历史数据)
interface Kline {
exchange: string;
symbol: string;
interval: KlineInterval;
openTime: number;
closeTime: number;
open: number;
high: number;
low: number;
close: number;
volume: number;
quoteVolume: number; // 成交额
takerBuyBaseVolume: number; // 主动买入量
takerBuyQuoteVolume: number; // 主动买入额
tradeCount: number; // 成交笔数
}
6. 完整实现示例
// src/data/pipeline/kline-synthesizer.ts
import { Subject, interval } from 'rxjs';
import { bufferTime, filter, map } from 'rxjs/operators';
type KlineInterval = '1m' | '5m' | '15m' | '30m' | '1h' | '4h' | '1d';
const INTERVAL_MS: Record<KlineInterval, number> = {
'1m': 60_000,
'5m': 300_000,
'15m': 900_000,
'30m': 1_800_000,
'1h': 3_600_000,
'4h': 14_400_000,
'1d': 86_400_000,
};
interface Trade {
price: number;
amount: number;
timestamp: number;
}
interface KlineBucket {
openTime: number;
open: number;
high: number;
low: number;
close: number;
volume: number;
tradeCount: number;
isClosed: boolean;
}
export class KlineSynthesizer {
private buckets = new Map<string, KlineBucket>();
/**
* 核心合成方法:输入一条 Trade,输出(可能)闭合的 K 线
*/
synthesize(
symbol: string,
interval: KlineInterval,
trade: Trade,
): KlineBucket | null {
const intervalMs = INTERVAL_MS[interval];
const bucketTime =
Math.floor(trade.timestamp / intervalMs) * intervalMs;
const key = `${symbol}:${interval}:${bucketTime}`;
let bucket = this.buckets.get(key);
if (!bucket) {
// 新时间桶:初始化
bucket = {
openTime: bucketTime,
open: trade.price,
high: trade.price,
low: trade.price,
close: trade.price,
volume: trade.amount,
tradeCount: 1,
isClosed: false,
};
this.buckets.set(key, bucket);
return null; // 新桶第一条,暂不 emit
}
// 更新当前桶
bucket.high = Math.max(bucket.high, trade.price);
bucket.low = Math.min(bucket.low, trade.price);
bucket.close = trade.price;
bucket.volume += trade.amount;
bucket.tradeCount += 1;
return null; // 桶未关闭
}
/**
* 关闭过期桶(由定时器调用),返回所有已关闭的 K 线
*/
closeExpiredBuckets(now: number): KlineBucket[] {
const closed: KlineBucket[] = [];
for (const [key, bucket] of this.buckets.entries()) {
if (!bucket.isClosed && now >= bucket.openTime + INTERVAL_MS['1m']) {
bucket.isClosed = true;
closed.push(bucket);
this.buckets.delete(key);
}
}
return closed;
}
/**
* RxJS 操作符:将 Trade 流转换为 K 线流
*/
transform(interval: KlineInterval) {
return (source: Subject<Trade>) => {
const output = new Subject<KlineBucket>();
// 每笔 Trade 更新桶
source.subscribe((trade) => {
this.synthesize('BTCUSDT', interval, trade);
});
// 定时检查过期桶(每秒一次)
interval(1000).subscribe(() => {
const closed = this.closeExpiredBuckets(Date.now());
closed.forEach((k) => output.next(k));
});
return output;
};
}
}
7. 性能考量
| 指标 | 数据量 | 说明 |
|---|---|---|
| 单币种 Trade/秒 | BTCUSDT 高峰期 | |
| 单币种 Ticker/秒 | ~10 tps | Binance WebSocket 推送频率 |
| 内存开销/币种 | 100 个并行时间桶 | |
| 单笔合成耗时 | < 1 μs | Node.js V8 优化后 |
| 10 币种 5 周期并行 | ~500 μs/轮 | 全部更新一次 |
优化建议:对于高频币种(BTC/ETH),可以采用 Trade 合成 + 1m K 线直接复用交易所推送的原始 K 线(无需自行合成)。
8. 与交易所 K 线的差异处理
| 差异项 | 交易所推送 K 线 | 本地合成 K 线 |
|---|---|---|
| 延迟 | 通常延迟 1~5 秒 | 实时(Trade 到达即更新) |
| 精度 | 包含所有成交数据 | 取决于 WebSocket 推送的 Trade 覆盖率 |
| 完整性 | 最终数据,不可变 | 可能因断线导致缺失(需补齐) |
| 自定义周期 | 仅支持标准周期 | 支持任意周期(如 3m、7m、自定义) |
| 多交易所对齐 | 各交易所时间不同 | 使用 UTC 统一对齐 |
最佳实践:本地合成 K 线用于 实时策略决策,交易所 K 线用于 回测和历史分析。两者结合使用。
TimescaleDB 数据存储方案
K 线数据具有典型的时序特征:持续写入、按时间范围查询、少有更新、数据量大。TimescaleDB 作为 PostgreSQL 的时序扩展,相比 InfluxDB 有以下核心优势:
| 对比维度 | TimescaleDB | InfluxDB |
|---|---|---|
| 基础数据库 | PostgreSQL 扩展 | 独立时序数据库 |
| SQL 兼容 | 完整 SQL 支持(JOIN、子查询、窗口函数) | 类 SQL 但不完全兼容 |
| 与业务数据关联 | K 线表和订单表可以在同一个 PG 实例中 JOIN | 需要跨数据库查询 |
| 数据压缩 | 列式压缩,压缩比 90%+ | 压缩比约 85% |
| 连续聚合 | 内置自动刷新物化视图 | 需要外部工具 |
| 保留策略 | 内置自动删除(数据保留策略) | 内置 |
| 生态集成 | 可与 PostGIS、pgvector 等 PG 扩展共存 | 独立生态 |
| 运维成本 | 复用 PG 运维经验 | 需独立运维 |
1. 表结构设计
-- 创建 TimescaleDB 扩展
CREATE EXTENSION IF NOT EXISTS timescaledb;
CREATE EXTENSION IF NOT EXISTS timescaledb_toolkit;
-- ============================================
-- 1. K 线主表(hypertable)
-- ============================================
CREATE TABLE klines (
time TIMESTAMPTZ NOT NULL, -- K 线开盘时间
exchange TEXT NOT NULL, -- 交易所 (binance/okx/bybit)
symbol TEXT NOT NULL, -- 交易对 (BTCUSDT/ETHUSDT)
interval TEXT NOT NULL, -- 周期 (1m/5m/15m/1h/4h/1d)
-- OHLCV
open NUMERIC(20,8) NOT NULL,
high NUMERIC(20,8) NOT NULL,
low NUMERIC(20,8) NOT NULL,
close NUMERIC(20,8) NOT NULL,
volume NUMERIC(20,8) NOT NULL,
-- 扩展字段
quote_volume NUMERIC(20,8), -- 成交额(计价币种)
taker_buy_base_vol NUMERIC(20,8), -- 主动买入成交量
taker_buy_quote_vol NUMERIC(20,8), -- 主动买入成交额
trade_count INTEGER, -- 成交笔数
is_closed BOOLEAN DEFAULT TRUE, -- K 线是否已关闭
-- 元数据
created_at TIMESTAMPTZ DEFAULT NOW(), -- 记录创建时间
updated_at TIMESTAMPTZ DEFAULT NOW(), -- 最后更新时间
-- 分区键
-- time 是分区列,symbol + interval 是分布列
UNIQUE (time, exchange, symbol, interval)
);
-- 转换为 hypertable(按时间和空间分区)
SELECT create_hypertable(
'klines',
'time', -- 时间分区列
chunk_time_interval => INTERVAL '1 day', -- 每分区 1 天数据
partitioning_column => 'exchange', -- 空间分区列
number_partitions => 4, -- 4 个空间分区
if_not_exists => TRUE
);
-- ============================================
-- 2. 索引设计
-- ============================================
-- 查询最频繁:按交易对+周期+时间范围查
CREATE INDEX idx_klines_lookup
ON klines (exchange, symbol, interval, time DESC);
-- 回测查询:按交易对+周期+时间范围
CREATE INDEX idx_klines_backtest
ON klines (symbol, interval, time ASC);
-- 最新 K 线查询
CREATE INDEX idx_klines_latest
ON klines (exchange, symbol, interval, time DESC)
WHERE is_closed = TRUE;
-- ============================================
-- 3. 数据压缩策略
-- ============================================
-- 启用压缩(已关闭的 K 线自动压缩)
ALTER TABLE klines SET (
timescaledb.compress,
timescaledb.compress_segmentby = 'exchange, symbol, interval',
timescaledb.compress_orderby = 'time DESC'
);
-- 自动压缩策略:K 线关闭 7 天后压缩
SELECT add_compression_policy('klines', INTERVAL '7 days', if_not_exists => TRUE);
-- ============================================
-- 4. 数据保留策略
-- ============================================
-- 1m K 线保留 30 天
SELECT add_retention_policy('klines', INTERVAL '30 days', if_not_exists => TRUE);
2. 连续聚合(Continuous Aggregates)
连续聚合是 TimescaleDB 最强大的功能之一 —— 自动从低周期 K 线聚合为高周期 K 线,无需手动维护。
-- ============================================
-- 5m K 线(从 1m 自动聚合)
-- ============================================
CREATE MATERIALIZED VIEW klines_5m
WITH (timescaledb.continuous) AS
SELECT
time_bucket('5 minutes', time) AS time,
exchange,
symbol,
'5m'::TEXT AS interval,
FIRST(open, time) AS open,
MAX(high) AS high,
MIN(low) AS low,
LAST(close, time) AS close,
SUM(volume) AS volume,
SUM(quote_volume) AS quote_volume,
SUM(taker_buy_base_vol) AS taker_buy_base_vol,
SUM(taker_buy_quote_vol) AS taker_buy_quote_vol,
SUM(trade_count) AS trade_count
FROM klines
WHERE interval = '1m'
GROUP BY time_bucket('5 minutes', time), exchange, symbol;
-- 自动刷新策略(每 1 分钟刷新最近 10 分钟数据)
SELECT add_continuous_aggregate_policy('klines_5m',
start_offset => INTERVAL '1 day',
end_offset => INTERVAL '10 minutes', -- 留 10 分钟给延迟数据
schedule_interval => INTERVAL '1 minute',
if_not_exists => TRUE
);
-- ============================================
-- 15m K 线
-- ============================================
CREATE MATERIALIZED VIEW klines_15m
WITH (timescaledb.continuous) AS
SELECT
time_bucket('15 minutes', time) AS time,
exchange, symbol, '15m'::TEXT AS interval,
FIRST(open, time), MAX(high), MIN(low), LAST(close, time),
SUM(volume), SUM(quote_volume),
SUM(taker_buy_base_vol), SUM(taker_buy_quote_vol),
SUM(trade_count)
FROM klines WHERE interval = '1m'
GROUP BY time_bucket('15 minutes', time), exchange, symbol;
SELECT add_continuous_aggregate_policy('klines_15m',
start_offset => INTERVAL '2 days',
end_offset => INTERVAL '30 minutes',
schedule_interval => INTERVAL '5 minutes',
if_not_exists => TRUE
);
-- ============================================
-- 1h K 线
-- ============================================
CREATE MATERIALIZED VIEW klines_1h
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', time) AS time,
exchange, symbol, '1h'::TEXT AS interval,
FIRST(open, time), MAX(high), MIN(low), LAST(close, time),
SUM(volume), SUM(quote_volume),
SUM(taker_buy_base_vol), SUM(taker_buy_quote_vol),
SUM(trade_count)
FROM klines WHERE interval = '1m'
GROUP BY time_bucket('1 hour', time), exchange, symbol;
SELECT add_continuous_aggregate_policy('klines_1h',
start_offset => INTERVAL '3 days',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '5 minutes',
if_not_exists => TRUE
);
-- ============================================
-- 1d K 线(含周线、月线可在查询时用 time_bucket)
-- ============================================
CREATE MATERIALIZED VIEW klines_1d
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 day', time) AS time,
exchange, symbol, '1d'::TEXT AS interval,
FIRST(open, time), MAX(high), MIN(low), LAST(close, time),
SUM(volume), SUM(quote_volume),
SUM(taker_buy_base_vol), SUM(taker_buy_quote_vol),
SUM(trade_count)
FROM klines WHERE interval = '1m'
GROUP BY time_bucket('1 day', time), exchange, symbol;
SELECT add_continuous_aggregate_policy('klines_1d',
start_offset => INTERVAL '7 days',
end_offset => INTERVAL '2 hours',
schedule_interval => INTERVAL '1 hour',
if_not_exists => TRUE
);
查询示例:
-- 查询 BTC 最近 100 根 1h K 线(自动走连续聚合)
SELECT * FROM klines_1h
WHERE symbol = 'BTCUSDT'
AND exchange = 'binance'
ORDER BY time DESC
LIMIT 100;
-- 查询 BTC 1h K 线的周线
SELECT
time_bucket('1 week', time) AS week,
FIRST(open, time), MAX(high), MIN(low), LAST(close, time),
SUM(volume)
FROM klines_1h
WHERE symbol = 'BTCUSDT'
GROUP BY week
ORDER BY week DESC;
-- 统计最近 7 天各币种波动率
SELECT
symbol,
MAX(high) / MIN(low) - 1 AS volatility
FROM klines_1h
WHERE time > NOW() - INTERVAL '7 days'
GROUP BY symbol
ORDER BY volatility DESC;
3. 空间与时间分区原理
hypertable: klines
┌─────────────────────────────────────────────────────┐
│ 时间线 │
│ │
│ chunk_1 (2024-01-01) chunk_2 (2024-01-02) ... │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ exchange=A │ │ exchange=A │ │
│ │ exchange=B │ │ exchange=B │ │
│ │ exchange=C │ │ exchange=C │ │
│ │ exchange=D │ │ exchange=D │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ 每个 chunk 内部按 exchange 做空间分区 │
│ chunk 大小 ≈ 1 天数据(自动调整) │
└─────────────────────────────────────────────────────┘
| 参数 | 值 | 说明 |
|---|---|---|
chunk_time_interval |
1 day |
每个 chunk 存储 1 天数据,约 100~500MB |
number_partitions |
4 | 按交易所分区,4 个空间分区 |
| 空间分区列 | exchange |
Binance/OKX/Bybit 等分布在不同分区 |
4. TypeScript 写入实现
// data/src/storage/timescaledb.ts
import { Pool } from 'pg';
interface KlineRecord {
time: Date;
exchange: string;
symbol: string;
interval: string;
open: number;
high: number;
low: number;
close: number;
volume: number;
quoteVolume: number;
takerBuyBaseVol: number;
takerBuyQuoteVol: number;
tradeCount: number;
isClosed: boolean;
}
export class TimescaleDBStorage {
private pool: Pool;
private batchBuffer: KlineRecord[] = [];
private batchSize: number;
private flushInterval: number;
constructor(config: {
connectionString: string;
batchSize?: number; // 批量写入条数,默认 500
flushIntervalMs?: number; // 最大等待时间,默认 1000ms
}) {
this.pool = new Pool({ connectionString: config.connectionString });
this.batchSize = config.batchSize ?? 500;
this.flushInterval = config.flushIntervalMs ?? 1000;
// 定时刷新缓冲区
setInterval(() => this.flush(), this.flushInterval);
}
/**
* 写入单条 K 线(加入缓冲区,批量写入)
*/
async write(kline: KlineRecord): Promise<void> {
this.batchBuffer.push(kline);
if (this.batchBuffer.length >= this.batchSize) {
await this.flush();
}
}
/**
* 批量刷新缓冲区
*/
async flush(): Promise<void> {
if (this.batchBuffer.length === 0) return;
const batch = this.batchBuffer.splice(0, this.batchSize);
const client = await this.pool.connect();
try {
// 使用 UNLOGGED 表写入时跳过 WAL 日志(提升写入性能)
await client.query('SET LOCAL timescaledb.enable_skip_scan = ON');
// 批量 UPSERT:已存在的 K 线只更新 close/high/low(增量更新)
const values = batch.map((k, i) => {
const offset = i * 14;
return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4},
$${offset + 5}, $${offset + 6}, $${offset + 7}, $${offset + 8},
$${offset + 9}, $${offset + 10}, $${offset + 11}, $${offset + 12},
$${offset + 13}, $${offset + 14})`;
}).join(',');
const params = batch.flatMap(k => [
k.time, k.exchange, k.symbol, k.interval,
k.open, k.high, k.low, k.close,
k.volume, k.quoteVolume,
k.takerBuyBaseVol, k.takerBuyQuoteVol,
k.tradeCount, k.isClosed,
]);
await client.query(`
INSERT INTO klines (
time, exchange, symbol, interval,
open, high, low, close,
volume, quote_volume,
taker_buy_base_vol, taker_buy_quote_vol,
trade_count, is_closed
) VALUES ${values}
ON CONFLICT (time, exchange, symbol, interval) DO UPDATE SET
high = GREATEST(klines.high, EXCLUDED.high),
low = LEAST(klines.low, EXCLUDED.low),
close = EXCLUDED.close,
volume = klines.volume + EXCLUDED.volume,
trade_count = klines.trade_count + EXCLUDED.trade_count,
is_closed = EXCLUDED.is_closed,
updated_at = NOW()
`, params);
} finally {
client.release();
}
}
/**
* 查询 K 线
*/
async query(options: {
exchange: string;
symbol: string;
interval: string;
startTime: Date;
endTime: Date;
limit?: number;
}): Promise<KlineRecord[]> {
const result = await this.pool.query(`
SELECT * FROM klines
WHERE exchange = $1
AND symbol = $2
AND interval = $3
AND time >= $4
AND time <= $5
ORDER BY time ASC
LIMIT $6
`, [
options.exchange,
options.symbol,
options.interval,
options.startTime,
options.endTime,
options.limit ?? 1000,
]);
return result.rows;
}
async close(): Promise<void> {
await this.flush();
await this.pool.end();
}
}
5. 写入性能优化策略
| 策略 | 说明 | 效果 |
|---|---|---|
| 批量写入 | 每 500 条或 1 秒批量写入一次 | 减少 99% 的数据库连接开销 |
| UPSERT | 用 ON CONFLICT DO UPDATE 处理同一 K 线的增量更新 |
避免重复写入 |
| 连接池 | pg.Pool 管理 10~20 个连接 |
控制并发,避免 PG 连接暴涨 |
| 压缩 | 关闭 7 天后自动压缩 | 存储空间减少 90%+ |
| chunk 对齐 | chunk 按天分区,避免跨分区查询 | 查询性能提升 10x |
| 索引 | 精确的复合索引覆盖所有查询模式 | 避免全表扫描 |
6. Python 读取实现(策略引擎侧)
# engine/common/storage.py
import asyncpg
from datetime import datetime
from typing import Optional
class TimescaleDBReader:
"""Python 策略引擎侧的 K 线读取器"""
def __init__(self, dsn: str):
self.dsn = dsn
self.pool: Optional[asyncpg.Pool] = None
async def connect(self):
self.pool = await asyncpg.create_pool(
self.dsn,
min_size=5,
max_size=20,
command_timeout=10,
)
async def get_klines(
self,
symbol: str,
interval: str,
start_time: datetime,
end_time: datetime,
exchange: str = "binance",
limit: int = 1000,
) -> list[dict]:
"""
获取 K 线数据(自动路由到连续聚合视图)
当 interval >= '1h' 时,自动使用连续聚合视图查询,
性能远优于扫描原始 1m 表。
"""
# 自动路由到对应的物化视图
view_map = {
"5m": "klines_5m", "15m": "klines_15m",
"30m": "klines_30m", "1h": "klines_1h",
"4h": "klines_4h", "1d": "klines_1d",
}
table = view_map.get(interval, "klines")
async with self.pool.acquire() as conn:
rows = await conn.fetch(
f"""
SELECT
time,
open,
high,
low,
close,
volume,
quote_volume,
trade_count
FROM {table}
WHERE exchange = $1
AND symbol = $2
AND interval = $3
AND time >= $4
AND time <= $5
ORDER BY time ASC
LIMIT $6
""",
exchange, symbol, interval,
start_time, end_time, limit,
)
return [dict(row) for row in rows]
async def get_latest_kline(
self,
symbol: str,
interval: str,
exchange: str = "binance",
) -> Optional[dict]:
"""获取最新一根 K 线"""
async with self.pool.acquire() as conn:
row = await conn.fetchrow(
"""
SELECT * FROM klines
WHERE exchange = $1
AND symbol = $2
AND interval = $3
AND is_closed = TRUE
ORDER BY time DESC
LIMIT 1
""",
exchange, symbol, interval,
)
return dict(row) if row else None
async def get_ohlcv_batch(
self,
symbol: str,
interval: str,
limit: int = 200,
exchange: str = "binance",
) -> list[tuple]:
"""
获取 OHLCV 数组(直接用于 pandas DataFrame 或 TA-Lib 计算)
返回格式: [(timestamp, open, high, low, close, volume), ...]
"""
view_map = {
"5m": "klines_5m", "15m": "klines_15m",
"1h": "klines_1h", "4h": "klines_4h", "1d": "klines_1d",
}
table = view_map.get(interval, "klines")
async with self.pool.acquire() as conn:
rows = await conn.fetch(
f"""
SELECT time, open, high, low, close, volume
FROM {table}
WHERE exchange = $1 AND symbol = $2 AND interval = $3
ORDER BY time DESC
LIMIT $4
""",
exchange, symbol, interval, limit,
)
# 正序返回
return [
(row['time'], row['open'], row['high'],
row['low'], row['close'], row['volume'])
for row in reversed(rows)
]
async def close(self):
if self.pool:
await self.pool.close()
7. 磁盘空间估算
-- 单币种单交易所 1m K 线行数
-- 1天 = 1440 行,1月 ≈ 43200 行,1年 ≈ 525600 行
-- 无压缩时:
-- 1 行 ≈ 180 bytes
-- 10 币种 × 1 年 × 180 B = 945 MB
-- 启用压缩后(压缩比约 92%):
-- 10 币种 × 1 年 × 180 B × 8% ≈ 75 MB
-- 50 币种 × 1 年 ≈ 375 MB
| 数据量 | 未压缩 | 压缩后 (92%) | 连续聚合额外 |
|---|---|---|---|
| 10 币种 / 1 年 | ~945 MB | ~75 MB | ~30 MB |
| 50 币种 / 1 年 | ~4.7 GB | ~375 MB | ~150 MB |
| 200 币种 / 1 年 | ~18.9 GB | ~1.5 GB | ~600 MB |
8. Docker Compose 配置
# docker-compose.yml
version: '3.8'
services:
timescaledb:
image: timescale/timescaledb:2-pg16
container_name: trade-timescaledb
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_DB: trade
POSTGRES_USER: trader
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
volumes:
- timescaledb-data:/var/lib/postgresql/data
- ./data/init-db:/docker-entrypoint-initdb.d # 自动执行建表 SQL
command: >
-c shared_buffers=1GB
-c effective_cache_size=3GB
-c maintenance_work_mem=256MB
-c work_mem=64MB
-c wal_buffers=64MB
-c random_page_cost=1.1
-c effective_io_concurrency=200
-c max_connections=50
healthcheck:
test: ["CMD-SHELL", "pg_isready -U trader -d trade"]
interval: 10s
timeout: 5s
retries: 5
volumes:
timescaledb-data:
初始化脚本:将上述建表 SQL 保存为
data/init-db/001_init.sql,容器首次启动时自动执行。
推荐的 npm 包
| 包名 | 用途 |
|---|---|
ws |
WebSocket 客户端(轻量、高性能) |
ccxt |
统一交易所 API(支持 100+ 交易所) |
pg |
PostgreSQL 客户端(TimescaleDB 兼容) |
ioredis |
Redis 客户端(支持集群、哨兵) |
zod |
运行时数据校验,与 Python Pydantic 对应 |
pino |
高性能结构化日志 |
rxjs |
响应式编程,优雅处理数据流 |
bull / bullmq |
Redis 任务队列,用于定时采集任务 |
technicalindicators |
技术指标计算(备选) |
2. 策略引擎 (engine/) — 🐍 Python
核心调度中心,负责策略的生命周期管理和信号分发。
子模块划分
| 子模块 | 职责 | 关键技术点 |
|---|---|---|
| 通用模块 | 策略基类、数据模型、日志、配置 | engine/common/ 目录,基础类型定义 |
| 策略管理器 | 策略注册、启动、停止、热加载 | 插件化架构、动态导入、importlib |
| 信号分发器 | 将策略产生的交易信号分发到交易执行模块 | 事件总线、消息队列 |
| 回测引擎 | 使用历史数据模拟策略执行,评估收益、回撤等指标 | vectorbt / backtrader、事件驱动 |
| 参数优化器 | 网格搜索 / 贝叶斯优化策略参数 | scipy.optimize、optuna、并行计算 |
策略基类设计
from common import BaseStrategy, StrategyConfig, Signal
from common.models import Kline, Ticker, OrderBook
class MyStrategy(BaseStrategy):
"""策略示例 — 所有策略继承 BaseStrategy"""
strategy_type = "my_strategy"
async def on_kline(self, kline: Kline) -> Signal | None:
...
3. 交易执行模块 (executor/) — 🐍 Python
负责将策略信号转化为实际订单,管理交易所连接和仓位。虽然数据采集在 TypeScript 层完成,但下单操作仍需在 Python 侧执行,因为策略引擎和风控逻辑同在 Python 侧,减少跨语言通信延迟。
子模块划分
| 子模块 | 职责 | 关键技术点 |
|---|---|---|
| 交易所适配器 | 封装不同交易所的 REST / WebSocket API,提供统一接口 | 适配器模式、限频控制、签名算法 |
| 订单管理器 | 下单、撤单、查询订单状态;支持限价单、市价单、条件单 | 订单状态机、幂等性、超时处理 |
| 仓位管理器 | 跟踪当前持仓、可用余额、未实现盈亏 | 实时同步、增量更新 |
| 执行算法 | TWAP、VWAP、冰山订单等高级执行算法(降低滑点) | 切片算法、时间加权 |
交易所适配器接口
class ExchangeAdapter(ABC):
"""交易所统一适配器接口"""
@abstractmethod
async def create_order(self, order: Order) -> OrderResult:
...
@abstractmethod
async def cancel_order(self, order_id: str) -> bool:
...
@abstractmethod
async def get_balance(self) -> dict[str, float]:
...
@abstractmethod
async def get_position(self, symbol: str) -> Position:
...
4. 风控模块 (risk/) — 🐍 Python
系统的安全防线,在交易前、交易中、交易后全链路控制风险。
| 功能 | 说明 |
|---|---|
| 资金管理 | 单笔下单量限制、总仓位比例限制、逐仓/全仓模式支持 |
| 最大回撤控制 | 当日/累计回撤超过阈值时自动暂停策略 |
| 异常检测 | 检测价格异常波动、交易所 API 异常、网络延迟过高 |
| 熔断机制 | 极端行情下自动停止所有交易,进入只平不开模式 |
| 订单频率限制 | 控制单位时间内的下单次数,防止过度交易 |
| 白名单/黑名单 | 限制可交易的币种和交易对 |
class RiskManager:
async def check_order(self, order: Order, context: TradingContext) -> RiskResult:
"""下单前风控检查"""
checks = [
self._check_max_position,
self._check_order_value,
self._check_order_frequency,
self._check_drawdown_limit,
self._check_price_deviation,
]
for check in checks:
result = await check(order, context)
if not result.passed:
return result
return RiskResult(passed=True)
5. 监控告警模块 (monitor/)
保障系统运行的可观测性。
| 功能 | 说明 |
|---|---|
| 指标收集 | 收集策略收益、胜率、夏普比率、最大回撤等绩效指标 |
| 系统监控 | CPU / 内存 / 网络延迟 / API 调用量 |
| 告警通知 | 通过钉钉、Telegram、飞书等发送告警 |
| 日志管理 | 结构化日志存储,支持按级别、模块检索 |
| Web 仪表盘 | 实时展示资金曲线、持仓、交易记录、策略状态等 |
6. API 网关 (api/)
对外提供统一接口,支持浏览器端和移动端访问。
| 功能 | 说明 |
|---|---|
| REST API | 策略管理、查询持仓、查看交易记录 |
| WebSocket | 实时推送行情、交易信号和通知 |
| 用户认证 | JWT 认证、API Key 管理 |
| 权限管理 | 多用户角色(管理员 / 只读用户) |
7. 配置与工具模块 (common/)
提供跨模块共享的基础设施。
| 模块 | 功能 |
|---|---|
| 配置 | 基于 pydantic-settings 的环境配置管理 |
| 日志 | 基于 loguru 的统一日志配置 |
| 工具 | 时间工具、重试装饰器、限流器、加解密工具 |
| 模型 | 共享的 Pydantic 数据模型(订单、K 线等) |
| 常量 | 交易所枚举、订单类型、时间周期等定义 |
TypeScript 数据模块详解
核心架构
┌─────────────────────────────────────────────────────────────┐
│ TypeScript 数据模块 │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Binance │ │ OKX │ │ Bybit │ │
│ │ WS Adapter │ │ WS Adapter │ │ WS Adapter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 统一行情流 (RxJS Observable) │ │
│ │ ┌───────────┐ ┌──────────┐ ┌─────────────────┐ │ │
│ │ │ ticker$ │ │ trade$ │ │ orderbook$ │ │ │
│ │ └─────┬─────┘ └────┬─────┘ └───────┬─────────┘ │ │
│ └────────┼──────────────┼────────────────┼─────────────┘ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ K 线合成器 (时间桶算法) │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │ 1m │ │ 5m │ │ 15m │ │ 1h │ ... │ │
│ │ └──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘ │ │
│ └────────┼────────┼────────┼────────┼─────────────────┘ │
│ ▼ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 数据清洗 & 异常检测 │ │
│ └───────────────┬──────────────────┬───────────────────┘ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────────┐ │
│ │ TimescaleDB 写入器 │ │ Redis Pub/Sub 发布者 │ │
│ └─────────────────────┘ └───────────┬─────────────┘ │
└────────────────────────────────────────┼───────────────────┘
│
▼
┌────────────────────┐
│ Python 策略引擎 │
│ (消费实时行情) │
└────────────────────┘
数据流生命周期
交易所 WS ──► TypeScript 采集器 ──► RxJS 管道
│
┌───────────┴───────────┐
│ │
▼ ▼
TimescaleDB 持久化 Redis Pub/Sub
│ │
▼ ▼
Grafana 查询 Python 策略消费
(历史分析) (实时交易)
TypeScript 数据模块目录结构
data/
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── src/
│ ├── index.ts # 模块入口
│ ├── config.ts # 配置(基于 zod 校验)
│ ├── logger.ts # 日志(pino)
│ │
│ ├── exchanges/ # 交易所适配器
│ │ ├── types.ts # 交易所统一接口
│ │ ├── base.ts # 抽象基类
│ │ ├── binance.ts # Binance 实现
│ │ ├── okx.ts # OKX 实现
│ │ └── bybit.ts # Bybit 实现
│ │
│ ├── pipeline/ # 数据管道
│ │ ├── kline-synthesizer.ts # K 线合成器
│ │ ├── cleaner.ts # 数据清洗
│ │ └── transformer.ts # 数据转换
│ │
│ ├── storage/ # 数据存储
│ │ ├── timescaledb.ts # TimescaleDB 写入器
│ │ └── postgres.ts # PostgreSQL 客户端(业务数据)
│ │
│ ├── publisher/ # 数据发布
│ │ ├── redis.ts # Redis Pub/Sub
│ │ └── nats.ts # NATS(可选)
│ │
│ ├── types/ # 类型定义
│ │ ├── market.ts # Ticker, Kline, Trade…
│ │ ├── exchange.ts # 交易所枚举
│ │ └── enums.ts # 常量枚举
│ │
│ └── utils/ # 工具函数
│ ├── retry.ts # 重试逻辑
│ ├── rate-limiter.ts # 限频器
│ └── time.ts # 时间工具
│
└── tests/
├── exchanges/
├── pipeline/
└── storage/
Python ↔ TypeScript 跨语言通信
┌──────────────────┐ Redis/NATS ┌──────────────────┐
│ 🟦 TypeScript │ ──────────────────────────► │ 🐍 Python │
│ 数据模块 │ Pub: "market:ticker" │ 策略引擎 │
│ │ Pub: "market:kline:1m" │ │
│ │ Pub: "market:trade" │ Sub: 消费行情 │
└──────────────────┘ └──────────────────┘
│ │
│ Redis Pub/Sub 频道设计 │
│ ───────────────────── │
│ market:ticker:{exchange}:{symbol} │
│ market:kline:{exchange}:{symbol}:{interval} │
│ market:trade:{exchange}:{symbol} │
│ system:heartbeat:{module} │
│ system:error:{module} │
│ │
└─────────────────────────────────────────────────┘
数据序列化格式:推荐使用 Protocol Buffers 或 MessagePack,比 JSON 更小、更快:
// TypeScript 侧 - 发布
const data = { symbol: 'BTCUSDT', price: 50000, timestamp: Date.now() };
const encoded = msgpack.encode(data); // 比 JSON 小 30-50%
await redis.publish('market:ticker:binance:BTCUSDT', encoded);
# Python 侧 - 消费
import msgpack
async with redis.pubsub() as pubsub:
await pubsub.subscribe('market:ticker:binance:BTCUSDT')
async for msg in pubsub.listen():
if msg['type'] == 'message':
data = msgpack.unpackb(msg['data'])
await strategy.on_ticker(Ticker(**data))
开发步骤
第一阶段:基础建设(第 1-2 周)
目标:搭建项目骨架,完成 TypeScript 数据模块的基础能力和 Python 项目基础。
| 步骤 | 任务 | 产出物 |
|---|---|---|
| 1.1 | 初始化 TypeScript 数据项目(data/),配置 package.json |
项目结构,ESLint + Prettier 配置 |
| 1.2 | 初始化 Python 项目,配置 poetry / uv 依赖管理 |
pyproject.toml,项目目录结构 |
| 1.3 | 定义共享类型:TypeScript types/ + Python engine/common/models.py |
双端对齐的数据模型 |
| 1.4 | 实现 TS 交易所适配器基类 + Binance 适配器(data/src/exchanges/) |
统一接口 + WebSocket 连接 |
| 1.5 | 实现 TS 行情采集器,WebSocket 订阅实时 ticker 和 K 线 | 实时行情流入 |
| 1.6 | 实现 TS K 线合成器(data/src/pipeline/kline-synthesizer.ts) |
多周期 K 线实时合成 |
| 1.7 | 实现 TS 数据存储模块,写入 TimescaleDB | 数据持久化 |
| 1.8 | 实现 TS → Redis 数据发布 | 实时行情推送到消息队列 |
| 1.9 | Python 侧实现配置管理 + 日志 + Redis 订阅消费者 | 消费端就绪 |
| 1.10 | Docker Compose 编排基础服务(TimescaleDB / Redis) | docker-compose.yml |
第二阶段:策略与回测(第 3-4 周)
目标:实现策略框架和回测引擎,能编写策略并进行回测验证。
| 步骤 | 任务 | 产出物 |
|---|---|---|
| 2.1 | 实现策略基类(engine/common/base.py) |
BaseStrategy 抽象基类 |
| 2.2 | 实现策略管理器(engine/manager.py),支持策略注册和生命周期 |
策略热加载、启动/停止控制 |
| 2.3 | 实现信号分发器(engine/signals.py) |
事件总线,策略到执行器的信号传递 |
| 2.4 | 实现回测引擎(engine/backtest.py) |
历史数据回测,收益曲线、回撤等指标 |
| 2.5 | 实现技术指标计算(data/indicators.py) |
MA、MACD、RSI 等常用指标 |
| 2.6 | 编写示例策略 1:双均线交叉策略 | 可运行的策略示例 |
| 2.7 | 编写示例策略 2:网格交易策略 | 可运行的策略示例 |
| 2.8 | 实现参数优化器(engine/optimizer.py) |
基于 Optuna 的参数搜索 |
第三阶段:交易执行与风控(第 5-6 周)
目标:连接实盘交易通道,实现完整的交易执行流程和风控体系。
| 步骤 | 任务 | 产出物 |
|---|---|---|
| 3.1 | 实现订单管理器(executor/order_manager.py) |
下单、撤单、订单状态更新 |
| 3.2 | 实现仓位管理器(executor/position_manager.py) |
实时仓位跟踪 |
| 3.3 | 实现交易状态机(executor/state_machine.py) |
订单生命周期管理(Created→Filled) |
| 3.4 | 实现重试与容错机制(executor/retry.py) |
网络异常自动重试、幂等性保证 |
| 3.5 | 实现风控管理器(risk/manager.py) |
资金管理、仓位限制、熔断逻辑 |
| 3.6 | 实现风控规则引擎(risk/rules.py) |
可扩展的风控规则链 |
| 3.7 | 整合 TS 数据模块 → Redis → Python 策略引擎 → 交易执行的端到端流程 | 完整的跨语言运行流水线 |
| 3.8 | 编写集成测试,模拟实盘环境验证 | 测试覆盖率报告 |
第四阶段:API 与监控(第 7-8 周)
目标:完善系统的可观测性和对外接口。
| 步骤 | 任务 | 产出物 |
|---|---|---|
| 4.1 | 搭建 FastAPI 项目(api/main.py) |
REST API 服务启动 |
| 4.2 | 实现 WebSocket 实时推送(api/ws.py) |
行情和交易数据实时推送 |
| 4.3 | 实现策略管理 API(api/routes/strategies.py) |
策略启停、参数修改接口 |
| 4.4 | 实现交易记录查询 API(api/routes/trades.py) |
交易历史查询 |
| 4.5 | 集成 Prometheus 指标(monitor/metrics.py) |
关键业务指标暴露 |
| 4.6 | 配置 Grafana 仪表盘 | 可视化资金曲线、交易频率、策略绩效 |
| 4.7 | 实现告警通知集成(monitor/alerts.py) |
Telegram / 钉钉通知 |
| 4.8 | JWT 用户认证与 API Key 管理 | 安全访问控制 |
第五阶段:优化与生产化(第 9-10 周)
目标:性能优化,系统加固,上线准备。
| 步骤 | 任务 | 产出物 |
|---|---|---|
| 5.1 | 性能优化:数据库批量写入、连接池调优、TS/Python 异步性能分析 | 性能测试报告 |
| 5.2 | 增加单元测试与集成测试,目标覆盖率 > 80% | 测试报告 |
| 5.3 | 编写部署文档(docs/deployment.md) |
部署手册 |
| 5.4 | 编写使用文档(docs/usage.md) |
用户手册 |
| 5.5 | CI/CD 流水线配置(GitHub Actions) — TS 和 Python 双管道 | 自动化测试、构建、部署 |
| 5.6 | 压力测试与稳定性测试 | 压测报告 |
| 5.7 | 生产环境部署与监控上線 | 线上系统 |
项目目录结构
trade/
├── pyproject.toml # Python 项目依赖与配置(Poetry / uv)
├── package.json # TS 项目根依赖(可选 monorepo 管理)
├── docker-compose.yml # 基础服务编排
├── Dockerfile # 应用容器化
├── .env.example # 环境变量模板
├── .gitignore
├── README.md
│
├── config/ # Python 配置文件
│ ├── __init__.py
│ ├── settings.py # pydantic-settings 全局配置
│ └── strategies/ # 策略配置文件
│ ├── ma_cross.yaml
│ └── grid_trading.yaml
│
├── data/ # 🟦 TypeScript 数据模块
│ ├── package.json
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ ├── src/
│ │ ├── index.ts # 模块入口
│ │ ├── config.ts # 配置
│ │ ├── exchanges/ # 交易所适配器
│ │ │ ├── types.ts
│ │ │ ├── base.ts
│ │ │ ├── binance.ts
│ │ │ ├── okx.ts
│ │ │ └── bybit.ts
│ │ ├── pipeline/ # 数据管道
│ │ │ ├── kline-synthesizer.ts
│ │ │ ├── cleaner.ts
│ │ │ └── transformer.ts
│ │ ├── storage/ # 数据存储
│ │ │ ├── timescaledb.ts # TimescaleDB K 线写入
│ │ │ └── postgres.ts # PostgreSQL 其他数据
│ │ ├── publisher/ # 数据发布
│ │ │ ├── redis.ts
│ │ │ └── nats.ts
│ │ ├── types/ # 类型定义
│ │ │ ├── market.ts
│ │ │ ├── exchange.ts
│ │ │ └── enums.ts
│ │ └── utils/
│ │ ├── retry.ts
│ │ ├── rate-limiter.ts
│ │ └── time.ts
│ └── tests/
│
├── engine/ # 🐍 Python 策略引擎
│ ├── __init__.py
│ ├── env.yaml # 引擎环境配置
│ ├── common/ # 引擎通用模块
│ │ ├── __init__.py
│ │ ├── base.py # BaseStrategy 基类
│ │ ├── models.py # 数据模型(Kline/Ticker/Trade/OrderBook)
│ │ └── logger.py # 结构化日志
│ ├── manager.py # 策略管理器
│ ├── signals.py # 信号分发器
│ ├── backtest.py # 回测引擎
│ └── optimizer.py # 参数优化器
│
├── executor/ # 🐍 Python 交易执行模块
│ ├── __init__.py
│ ├── exchange.py # 交易所适配器(基类+实现)
│ ├── order_manager.py # 订单管理器
│ ├── position_manager.py # 仓位管理器
│ ├── state_machine.py # 订单状态机
│ └── retry.py # 重试与容错
│
├── risk/ # 🐍 Python 风控模块
│ ├── __init__.py
│ ├── manager.py # 风控管理器
│ └── rules.py # 风控规则
│
├── monitor/ # 监控告警模块
│ ├── __init__.py
│ ├── metrics.py # Prometheus 指标
│ ├── alerts.py # 告警通知
│ └── dashboard.py # 仪表盘数据聚合
│
├── api/ # API 网关
│ ├── __init__.py
│ ├── main.py # FastAPI 入口
│ ├── ws.py # WebSocket 处理
│ ├── auth.py # 用户认证
│ └── routes/ # 路由
│ ├── __init__.py
│ ├── strategies.py
│ ├── trades.py
│ └── account.py
│
├── strategies/ # 🐍 Python 策略实现
│ ├── __init__.py
│ ├── ma_cross.py # 双均线交叉策略
│ ├── grid_trading.py # 网格交易策略
│ └── arbitrage.py # 套利策略(可选)
│
├── common/ # 🐍 Python 公共基础设施(跨模块共享配置、工具等)
│ ├── __init__.py
│ ├── config.py # 全局配置
│ ├── constants.py # 常量定义
│ └── utils.py # 工具函数
│
├── tests/ # 🐍 Python 测试
│ ├── __init__.py
│ ├── data/
│ ├── engine/
│ ├── executor/
│ ├── risk/
│ └── conftest.py # pytest fixtures
│
└── docs/ # 文档
├── deployment.md
├── usage.md
└── api_reference.md
部署与运维
服务组件依赖
┌──────────────────┐ ┌──────────────┐ ┌─────────────┐
│ TS 数据模块 │ │ PostgreSQL │ │ Grafana │
│ (Bun 进程) │ │ (业务数据) │ │ (可视化) │
└──────┬───────────┘ └──────────────┘ └──────┬──────┘
│ │
│ ┌──────────────┐ │
├────────────▶ TimescaleDB │◀────────────┤
│ │ (时序存储) │ │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ │
├────────────▶ Redis ├─────────────┤
│ │ (队列/缓存) │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────┐ │
└────────────▶ Python 引擎 │ │
│ (策略+执行) │ │
└──────────────┘ │
容器化方案
# Dockerfile.data — TypeScript 数据模块
FROM node:20-alpine AS builder
WORKDIR /app
COPY data/package*.json ./
RUN npm ci
COPY data/ .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
# Dockerfile.engine — Python 业务引擎
FROM python:3.11-slim
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && poetry install --no-dev
COPY . .
CMD ["poetry", "run", "python", "-m", "engine.main"]
关键运维指标
- API 延迟:单次下单 < 500ms(网络延迟不计)
- 数据延迟:行情从交易所到 Redis < 500ms(TypeScript 采集)
- 系统可用性:核心交易链路 > 99.9%
- 策略执行周期:秒级 tick 策略 < 100ms / 次
- 跨语言通信延迟:Redis Pub/Sub < 1ms(同机部署)
注意事项与风险提示
⚠️ 风险声明:数字货币交易具有高风险,本系统仅为技术实现,不构成任何投资建议。使用前请充分了解风险。
技术注意事项
- API 限频:各交易所均有严格 API 限频,需实现本地限流器
- WebSocket 重连:网络波动导致断线,需实现心跳检测和自动重连(指数退避策略)
- 数据一致性:订单状态需轮询确认,避免因异步回调丢失状态
- 时间同步:服务器需 NTP 同步,防止时间偏差导致下单失败
- 日志安全:避免记录 API Secret 等敏感信息到日志
- 灰度发布:新策略先在模拟盘运行,验证通过后方可接入实盘
- 资金隔离:不同策略的资金账户严格隔离,防止交叉影响
- 跨语言类型一致性:TypeScript 的
zodschema 和 Python 的Pydanticmodel 需保持同步,建议用自动化脚本生成或共享 schema 文件
开发建议
- 模块化开发:各模块独立开发、独立测试,通过接口契约协作
- 代码质量:TS 用
ESLint+Prettier,Python 用ruff+mypy - 版本控制:配置文件和策略参数不应硬编码,使用
.env或配置文件 - 日志先行:先写好日志,再写业务逻辑,便于调试
- 单元测试:核心逻辑(风控、订单状态机、K 线合成器)必须有单元测试覆盖
- 模拟盘先行:实盘前至少 2 周模拟盘验证策略稳定性
- 本地开发:Python 和 TS 模块分别
npm run dev/poetry run dev独立开发调试 - schema 驱动:考虑使用 Protocol Buffers 的
.proto文件同时生成 TS 和 Python 代码,保证类型完全一致
许可证
MIT License