跳到主要内容

历史回放

全深度流推送的每个事件也会写进按小时的归档。拉一个小时,用消费实时连接的同一套代码回放它,就是一次回测。免费,无需密钥。

https://replay.shrine.trade/pump/YYYY/MM/DD/HH.jsonl.zst

小时为 UTC。文件在该小时结束后几分钟出现,归档从开启当天开始;更早的小时不存在。

浏览存储桶

归档放在一个公开的 Cloudflare R2 存储桶里。对象存储没有目录列表,所以存储桶自己维护一份:https://replay.shrine.trade/pump/index.json 列出它持有的每个小时,最早的在前,每次上传后重写。

{ "base": "https://replay.shrine.trade/pump", "pattern": "<base>/<hour>.jsonl.zst", "count": 312, "hours": ["2026/09/11/07", "2026/09/11/08", "…"] }

basehours 里的一项和 .jsonl.zst 拼起来就是文件地址。要全部拉取就遍历 hours;要补缺口就找缺少的那一项。

格式

每行一个 JSON 对象,zstd 压缩。每一行就是实时 stream 事件,多一个字段:localTimestamp,我们的服务器看到该交易的毫秒时间,实时流里没有。其他一切,signatureblocktimestampactionprotocolmintbreakdowntradersInvolved、储备和发射权限,都如流页面所述。

一个小时压缩后几百 MB,解压后几 GB。请流式读取,不要整个载入。

直接复制

// Node 22.15+ 的 zlib 自带 zstd。更老的 Node:npm install fzstd。
import { createZstdDecompress } from "node:zlib";
import { createInterface } from "node:readline";
import { Readable } from "node:stream";

const url = "https://replay.shrine.trade/pump/2026/09/11/07.jsonl.zst";
const res = await fetch(url);
const lines = createInterface({ input: Readable.fromWeb(res.body).pipe(createZstdDecompress()) });

let buys = 0;
for await (const line of lines) {
const e = JSON.parse(line);
if (e.action === "buy" && e.protocol === "PUMPFUN") buys++;
}
console.log("pump.fun buys that hour:", buys);

说明

  • 和实时一样的形状。 针对 subscribe_stream 写的代码在归档上原样可用;喂给它行而不是 socket 事件。
  • 尽早过滤。 每个发射台的每一笔交易都在里面。流式读取时只留你关心的协议和动作,其余丢掉。
  • 顺序。 行按观察到的顺序排列,即区块顺序,偶有相邻互换。需要严格时按 block 再按 signature 排序。
  • 缺口。 采集器重启可能丢掉一小时内的一两分钟。没有标记;需要知道时比较 block 号。