「先把 tick 数据全存下来,以后要用再说」——这句话在项目早期听起来很稳妥,但它会变成一笔持续增长的账单。tick 数据的体积增长是标的数量 × 行情活跃度 × 时间的三重乘积,没有任何一项会自己变小。
这篇文章用一组明确的假设算一遍:1 万个标的、存一年,到底要多少磁盘、花多少钱,以及怎么把这个数字压下来。
先定义假设
为了避免结论含糊,先把假设写清楚。这些是行业里常见的取值,你可以按自己的情况替换:
| 参数 | 取值 | 说明 |
|---|---|---|
| 标的数量 | 10,000 | 覆盖多市场组合监控的规模 |
| 每标的 tick 频率 | 2 条/秒 | 活跃标的的平均水平,冷门标的远低于此 |
| 每条 tick 字段 | 标的、时间、最新价、买一、卖一、成交量 | 对应行情接口的 tick 字段集 |
| 保留周期 | 365 天 | 全年不间断 |
| 对象存储单价 | $0.023 / GB / 月 | 主流云厂商标准存储的常见档位 |
第一步:算出一年的 tick 总量
先把量级算出来,后面才有讨论的基础:
- 每秒 tick 数:10,000 标的 × 2 条 = 20,000 条/秒
- 每日 tick 数:20,000 × 86,400 秒 = 17.28 亿条/天
- 每年 tick 数:17.28 亿 × 365 = 约 6,307 亿条/年
6,307 亿条。这个数字本身没有意义,有意义的是它乘以「每条多少字节」之后的结果。
第二步:三种存储格式的体积差异
同样 6,307 亿条数据,用不同格式存下来,体积能差 7 倍以上:
| 格式 | 每条约 | 一年总体积 | 说明 |
|---|---|---|---|
| JSON 文本 | 180 字节 | 约 113.5 TB | 字段名重复存储,浮点数以文本表示 |
| 定长二进制 | 48 字节 | 约 30.3 TB | 省掉字段名,数值按定长编码 |
| 列式 + 压缩 | 24 字节 | 约 15.1 TB | 字典编码 + 时间戳增量编码 + 压缩 |
差距的来源很清楚:JSON 把字段名和引号重复写了 6,307 亿次,而定长二进制与列式存储不需要。列式存储还能利用时间戳的单调性和价格的低波动性做增量与字典编码,进一步压缩。
第三步:换算成钱
把体积乘以对象存储单价($0.023/GB/月):
| 格式 | 月存储成本 | 年存储成本 |
|---|---|---|
| JSON 文本(113.5 TB) | 约 $2,611 | 约 $31,334 |
| 定长二进制(30.3 TB) | 约 $696 | 约 $8,356 |
| 列式 + 压缩(15.1 TB) | 约 $348 | 约 $4,178 |
换句话说,仅仅因为选择了 JSON 落库,一年的存储账单就多出两万多美元。而这还只是存储费用,没有算写入吞吐、查询算力和备份。
需要说明的是,这里的单价是标准存储档位。如果改用低频访问或归档档位,单价可以显著下降,但取回延迟和取回费用会上升——这就是下一节要讲的取舍。
为什么 JSON 落库是最贵的选择
JSON 在 API 传输层是完全合理的选择:可读、调试方便、各语言原生支持。但把它直接当作持久化格式会带来三重成本:
- 存储体积:字段名与结构符号被重复存储,是纯粹的开销。
- 写入吞吐:解析 JSON 比解析定长二进制慢一个量级,在每秒两万条的写入压力下,CPU 会成为瓶颈。
- 查询算力:列式存储可以只读需要的列;JSON 必须整行解析,回测查询的扫描量成倍增加。
合理做法是:传输用 JSON,落库前转成列式格式。 在写入管道里加一个转换层,成本远低于长期多付的存储与算力费用。
分层存储:不是所有数据都需要在线
另一个常见的浪费是「把全部历史都放在最快的存储上」。实际访问模式是有规律的:
- 近 7 天:高频访问,用于实时看板与近期回测,放在低延迟存储。
- 近 90 天:中频访问,用于策略迭代,放在标准对象存储。
- 90 天以上:低频访问,主要用于历史回测复现,放在归档档位。
按这个分层,只有约 20% 的数据落在最贵的档位,整体账单能显著下降。代价是归档数据的取回需要等待,所以分层策略必须和你的回测频率匹配——如果团队每周都要跑一年期回测,归档档位就不合适。
更根本的做法:不要全存
最后也是最有效的办法:先聚合,再决定存什么原始数据。
- 按需求决定粒度:大部分策略只需要分钟线,不需要 tick。先存 1 分钟 K 线,体积能下降两个数量级。
- 选择性存原始数据:只对核心标的保留 tick,其余存聚合结果。
- 原始 tick 设保留期:例如只保留 90 天原始数据,之后降级为聚合数据。
在动手扩容之前,先问一句:这些 tick 数据里,实际被查询过的比例是多少?很多团队会发现答案低于 5%。
用 Python 算你自己的数字
TICKS_PER_SEC = 2
SECONDS_PER_DAY = 86_400
DAYS = 365
STORAGE_USD_PER_GB_MONTH = 0.023
# 每条约占用字节数:JSON 文本 / 定长二进制 / 列式压缩
BYTES_PER_TICK = {"json": 180, "binary": 48, "columnar": 24}
def annual_cost(symbols: int, ticks_per_sec: int = TICKS_PER_SEC) -> None:
total_ticks = symbols * ticks_per_sec * SECONDS_PER_DAY * DAYS
print(f"标的 {symbols:,} 个 → 年 tick 总量 {total_ticks / 1e8:.0f} 亿条")
print(f"{'格式':<10}{'体积':>12}{'月成本':>12}{'年成本':>14}")
for name, size in BYTES_PER_TICK.items():
tb = total_ticks * size / 1e12 # 十进制 TB
monthly = tb * 1000 * STORAGE_USD_PER_GB_MONTH # 1 TB = 1000 GB
print(f"{name:<10}{tb:>10.1f} TB{monthly:>11,.0f} ${monthly * 12:>13,.0f} #34;)
annual_cost(10_000)
# 标的 10,000 个 → 年 tick 总量 6307 亿条
# 格式 体积 月成本 年成本
# json 113.5 TB $2,611 $31,330
# binary 30.3 TB $696 $8,354
# columnar 15.1 TB $348 $4,177
把 symbols 和 ticks_per_sec 换成你的真实取值,就能得到自己的账单预估。建议在数据管道设计阶段就跑一遍这个计算——它的成本是几分钟,而选错格式的代价是持续数年的账单。
常见问题
tick 数据到底要不要全量保留?
大多数团队不需要。先明确哪些策略真正依赖 tick 级数据,只对这部分标的保留原始数据,其余存分钟级聚合结果即可。
为什么列式存储能压到 24 字节一条?
因为利用了数据的结构性:时间戳单调递增可以做增量编码,标的代码和价格档位可以字典编码,数值列连续存储便于压缩。这些手段在逐条 JSON 里都无法使用。
用数据库直接存 tick 合适吗?
取决于规模。每秒两万条的写入压力下,通用行式数据库通常撑不住,需要时序数据库或列式文件格式配合对象存储。数据管道的整体设计可参考行情数据库实践。
归档存储能省多少?
单价通常比标准存储低一个数量级,但取回需要等待且可能产生取回费用。适合历史回测复现这类低频场景,不适合每周都要跑全量回测的团队。
不同市场的 tick 量差异大吗?
很大。加密市场 7×24 连续交易,tick 频率可能非常高;而成交清淡的股票每分钟可能只有几笔。建议按市场分别估算,不要用统一的平均值——总量通常由最活跃的那部分主导。
压缩会不会影响回测精度?
不会。列式压缩是无损的,解码后与原始值完全一致。影响精度的因素是复权处理与数据对齐,而不是存储格式。
下一步
先算清自己的数字,再决定格式与分层策略。tick 数据的字段定义见 tick 数据,落库方案可参考 行情数据库实践,回测场景的数据准备流程见 回测数据准备指南。如果你的方案是「历史数据靠 API 按需拉取、只在本地保留近期数据」,那 套餐额度 会直接影响可行规模——流式接口的推送不按条计费,适合持续采集。