QuestDB 算法交易实战:读懂市场语言的架构设计

MarketMaker.cc Team
量化研究与策略

MarketMaker.cc Team
量化研究与策略
免责声明:本文内容仅供教育和参考目的,不构成任何财务、投资或交易建议。加密货币交易存在重大亏损风险。
大家好!今天我们开启一个关于 QuestDB 的三篇系列深度解析——QuestDB 是一款开源时序数据库,正在悄然成为现代交易基础设施的核心支柱。这不是又一篇"十大数据库"的盘点文章,我们将深入技术细节,因为算法交易就是这样要求的。
如果你曾为 InfluxDB 的高基数限制所困扰,在 TimescaleDB 处理 tick 数据时的性能开销上吃过亏,或者疑惑为何 PostgreSQL 无法跟上每秒百万级写入的速度——这个系列正是为你而写。
每一个算法交易系统——从简单的网格机器人到完整的高频交易引擎——都有同一个根本依赖:数据。具体而言,是以极高速度到达、需要实时查询的时间有序数据。
传统关系型数据库并非为此而生。它们擅长 ACID 事务和跨规范化模式的复杂连接,但面对金融市场所特有的大量追加写入、按时间分区的工作负载,却力不从心。结果是你在与数据库搏斗,而非让它为你服务。
时序数据库颠覆了这一范式。它们假定你的数据带有时间戳,数据大致按顺序到达,并且查询几乎总是涉及时间范围。QuestDB 在此基础上更进一步——它是专门针对资本市场设计的。其工程团队来自一线投资银行(美国银行、瑞银、汇丰),这一点在每一个设计决策中都有所体现。
QuestDB 是一款采用零 GC Java、C++ 和 Rust 编写的开源(Apache 2.0)时序数据库。"零 GC"至关重要:核心引擎完全绕过 Java 的垃圾回收器,手动管理内存,从而消除了大多数 JVM 系统中令人头痛的不可预测延迟抖动。
值得关注的关键性能特征:
但原始数字只揭示了故事的一部分。QuestDB 对交易系统真正令人着迷的地方在于它如何存储和查询数据。
这正是 QuestDB 架构优雅之处所在。数据流经三个不同的层次,每层针对不同的访问模式进行优化:
写入的数据首先进入预写日志(Write-Ahead Log)。这是超低延迟的追加写入层。每次写入在任何处理发生之前都会被持久化——在崩溃和断电情况下保证数据不丢失。WAL 是纯顺序写入的,这使其与现代 SSD 和 NVMe 驱动器完美契合。
对于交易系统,这意味着你的行情数据写入管道可以毫无顾虑地向 QuestDB 高速写入数据,无需担心写入放大或锁争用。无论是接收来自 50 家加密交易所的 WebSocket 更新,还是处理海量 FIX 消息,WAL 都能全部吸纳。
WAL 还会异步地将数据传送至对象存储,使新副本能够快速启动并读取相同的历史记录——这对于生产交易环境中的灾难恢复至关重要。
异步地,数据会被按时间排序、去重,并写入 QuestDB 的原生列式格式。该格式按时间分区(根据数据量按小时、天、周或月分区),并可立即查询。
列式布局正是 QuestDB 查询性能的关键所在。当你查询过去一小时内 BTC-USD 的平均价格时,引擎只读取相关时间分区中的 price 列,而非整行数据。结合跨多核的 SIMD 向量化执行,这带来了亚毫秒级的查询时间,使实时仪表板和实时策略计算成为可能。
每张表按列分别存储为独立文件,固定大小类型每列一个文件,可变大小类型(如 VARCHAR)使用两个文件。这种布局专门针对时序分析中占主导地位的顺序扫描而设计。
这是成本管理与互操作性的交汇点。较旧的分区会自动转换为 Apache Parquet 格式并传送至对象存储(S3、Azure Blob、GCS)。但——这是关键的创新——你仍然可以通过 QuestDB 的 SQL 接口透明地查询它们。查询规划器无缝跨越三个层次。
对于算法交易者,这意味着你可以在不支付 TB 级 SSD 存储费用的情况下,让多年的历史 tick 数据随时可用于回测。你的 Python 回测框架可以通过 Polars、Pandas 或 Spark 直接读取相同的 Parquet 文件,无需导出数据库。你的机器学习训练管道可以通过 Arrow/ADBC 访问相同数据进行内存处理。零供应商锁定。
这与将数据锁定在单一查询接口背后的专有数据库格式相比,是一个截然不同的价值主张。
QuestDB 的模式设计理念围绕几个与交易数据高度契合的关键概念展开:
每张时序表都需要一个指定的时间戳列。这不仅仅是元数据——它决定了物理存储顺序并支持分区裁剪。没有它,你将失去 QuestDB 的大部分性能优势:
CREATE TABLE trades (
timestamp TIMESTAMP,
symbol SYMBOL,
side SYMBOL,
price DOUBLE,
quantity DOUBLE
) TIMESTAMP(timestamp) PARTITION BY DAY;
SYMBOL 类型是 QuestDB 对高基数字符串问题的解答。"BTC-USD"或"ETH-USDT"等交易对以整数索引字典条目的形式存储。在 SYMBOL 列上的过滤和分组比在 VARCHAR 上快得多——QuestDB 在编译时将字符串比较解析为整数比较。
如果你在从 100 多家交易所写入数以千计交易对的数据,仅这一优化就可能是查询耗时 5 毫秒与 500 毫秒之间的差距。
分区大小应与数据量相匹配。高频 tick 数据(每天每个交易对数百万行)应使用 PARTITION BY HOUR。低量的日终数据则使用 PARTITION BY MONTH 即可。目标是在保持单个分区可管理的同时实现高效裁剪:
-- 高频 tick 数据
CREATE TABLE ticks (...) TIMESTAMP(ts) PARTITION BY HOUR;
-- 低量日线价格
CREATE TABLE eod_prices (...) TIMESTAMP(ts) PARTITION BY MONTH;
在真实的交易系统中,重复数据是不可避免的。网络重传、为提高可靠性而建立的冗余交易所连接、恢复期间历史数据的重放——所有这些都会产生重复数据。QuestDB 原生处理这一问题:启用后,去重会用新版本替换匹配的行,只有真正的新行才会被插入。
性能影响取决于你的数据模式。如果各行的时间戳大多唯一,开销很小。最苛刻的情况是许多行共享相同时间戳并需要基于额外列进行去重——这在多个价格级别同时更新的订单簿快照中很常见。
QuestDB 的生产客户包括 B3(拉丁美洲最大的证券交易所)、One Trading(受监管的加密交易所,每秒写入量高达 400 万行)、Laser Digital(野村集团),以及众多一线银行和对冲基金。
一些实际部署说明:
企业版新增了 RBAC(包括列级权限)、TLS 加密、自动复制和故障转移、分级存储到云对象存储,以及带 SLA 的专属支持。对于受监管的环境,这些是必备条件。
在第 2 篇中,我们将深入探讨 QuestDB 的时序 SQL 扩展——SAMPLE BY、ASOF JOIN、HORIZON JOIN、WINDOW JOIN 和 LATEST ON——并附上真实的交易示例。这些不是对标准 SQL 的渐进式改进,而是从根本上不同的工具,能够消除整类复杂查询。
在第 3 篇中,我们将介绍实际交易应用:用于实时 OHLC 的物化视图、用于订单簿分析的 2D 数组,以及基于 QuestDB 的算法交易平台完整架构。
敬请期待。
@software{soloviov2025questdb_algotrading_p1,
author = {Soloviov, Eugen},
title = {QuestDB for Algorithmic Trading: Architecture That Speaks the Language of Markets},
year = {2025},
url = {https://marketmaker.cc/en/blog/post/questdb-algotrading-architecture},
version = {0.1.0},
description = {Deep dive into QuestDB's three-tier storage architecture and schema design for algorithmic trading systems.}
}