Solana 上的高频交易,和传统交易所的 HFT 不是同一件事。把那边的打法直接搬过来,是很多团队浪费掉头六个月的方式。
在撮合引擎上,延迟按微秒算,每一微秒都算数。而 Solana 是按 slot 离散推进的。★比对手快那么一点点,如果你们落在同一个 slot 里,什么都不会改变。★
什么东西被"量化"了
Solana 以固定节奏出块。你的交易落在 slot N 或者 slot N+1 —— 在同一个 slot 内更早到达,没有任何额外奖励。
这带来一个大家常忽略的结论:
在一个 slot 内把延迟砍半 → ★可能什么都不会变★
刚好省下足够早落一个 slot → ★改变一切★
后者跨过了 slot 边界,前者没有。★只有把你推过边界的优化才有回报★ —— 所以有用的问题不是"我有多快",而是**"我落在哪个 slot"**。
于是第一个该测的不是毫秒,是 slot 距离:
const submitSlot = await connection.getSlot("processed");
const sig = await connection.sendRawTransaction(raw, {
skipPreflight: true,
maxRetries: 0,
});
const { value } = await connection.getSignatureStatuses([sig], {
searchTransactionHistory: true,
});
log({
submitSlot,
landSlot: value[0]?.slot ?? null,
slotGap: value[0]?.slot ? value[0].slot - submitSlot : null,
});
如果你已经在测这个、而 slot 差就是问题,免费领个 BoltTx key 改一行就能拿来做对比。
时间实际花在哪
一个 Solana HFT 循环的时间预算,大致是这样分布的:
| 环节 | 数量级 | 归你管吗 |
|---|---|---|
| 签名(ed25519) | 微秒 | ★已经可以忽略★ |
| 构造指令 | 微秒 | 已经可以忽略 |
| 序列化 | 微秒 | 已经可以忽略 |
| ★到端点的网络★ | ★毫秒★ | ★归你 —— 地理位置、keep-alive★ |
| ★端点到验证节点★ | ★毫秒★ | ★选服务商★ |
| 等待被收录 | ★slot★ | 费用、重试、路由 |
★最上面三项加起来占总时间不到百分之一。★ 而"用更快的语言重写机器人",优化的正是这三项。
这是 Solana HFT 里最常见的资源错配:团队把 TypeScript 机器人移植到 Rust,快了几百微秒,然后落在和之前完全一样的那个 slot 里。
Rust 是个合理的选择,但理由是别的 —— 内存行为可预测、没有偶尔吞掉一整个 slot 的 GC 停顿。★但"Rust 更快"本身,在 Solana 上不构成一个延迟论据。★
真正能跨过边界的优化
地理位置。 网络往返往往是你能控制的最大单项。从一台离端点近的机器提交、对比跨半个地球提交,价值超过所有代码优化的总和。
连接复用。 冷 TLS 连接意味着字节开始传之前要先完整握手。把它去掉:
import { Agent } from "undici";
// 热连接池,热路径上不做握手。
const agent = new Agent({
keepAliveTimeout: 60_000,
connections: 16,
});
在复用连接上,HTTPS 的成本和纯 HTTP 基本一样。★对持续发送的机器人来说,握手是你每一笔都在白付的延迟。★
热路径上不碰网络。 从"决定"到"提交"之间的每一次往返,都可能让你丢掉一个 slot:
// 后台刷新。绝不在用时才取 blockhash。
let cached = await connection.getLatestBlockhash("confirmed");
setInterval(async () => {
cached = await connection.getLatestBlockhash("confirmed");
}, 5_000);
priority fee 按实时行情算。 写死的 microLamports 两个方向都错:
import { ComputeBudgetProgram } from "@solana/web3.js";
const recent = await connection.getRecentPrioritizationFees({
lockedWritableAccounts: writableAccounts, // ★费用是按账户竞争的★
});
const fees = recent.map((r) => r.prioritizationFee).sort((a, b) => a - b);
const median = fees[Math.floor(fees.length / 2)] ?? 0;
instructions.unshift(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: Math.max(median * 2, 5_000),
}),
ComputeBudgetProgram.setComputeUnitLimit({ units: 200_000 }),
);
吞吐是另一个问题
HFT 通常既意味着低延迟、也意味着高吞吐,而吞吐会撞上一批延迟优化碰不到的约束。
限流。 每秒几百次读取很快就会撞档位。要激进地批量:
// 每 100 个账户一次调用,不是每个账户一次。
const infos = await connection.getMultipleAccountsInfo(pubkeys);
一批交易复用同一个 blockhash。 拿一个 blockhash 签很多笔,尾部必然过期。中途刷新,或者签在那个持续被刷新的缓存值上。
长时效交易用 nonce 账户。 如果一笔交易必须在约 150 个区块的窗口之外仍然有效,durable nonce 会替代 recent blockhash、彻底去掉过期问题。代价是多一个账户和一条 advanceNonce 指令。
账户写入竞争。 你自己的两笔交易如果在同一个 slot 里写同一个账户,会互相串行化。量一大,你会和自己竞争。
Rust、TypeScript,以及真正的差别
既然这是争论最多的决策,值得说具体点。
★原始执行速度不是差异所在★ —— 因为 HFT 循环里受计算约束的那部分,相对网络时间已经可以忽略。
真正有差别的是:
垃圾回收。 Node.js 的一次 major GC 停顿偶尔会超过一个 slot。少见,但它发生在最糟的时刻 —— 高负载下、你的队列最深的时候。
尾部可预测性。 Rust 的时间分布更紧。对一个活在尾部而不是中位数的策略来说,这种一致性才是真正的论据。
连接控制。 更底层的 HTTP 控制,更容易保证热路径上没有握手。
★如果你的 TypeScript 机器人已经稳定在两个 slot 内落块,用 Rust 重写不会提升成交率。★ 承诺重写之前,先测 slot 分布。
上链应该是什么水平
我们投递节点上的真实交易:中位确认 336 毫秒 —— 不到一个 slot。
★对 HFT 来说,要看的是尾部的形状,不是中位数。★ 一条"通常很快但偶尔很慢"的路径,恰好会错过你搭这套系统就是为了抓的那些竞争激烈的机会。
该埋哪些点
log({
decideAt, // 策略做出决定
submitAt, // sendRawTransaction 返回
submitSlot,
landSlot,
slotGap: landSlot - submitSlot, // ★核心指标★
blockhashAge: submitAt - blockhashFetchedAt,
feeMicroLamports,
congestionBucket, // busy 还是 calm
});
按拥堵分桶,对比分布而不是平均值。★清闲时稳、高负载时拉大的 slot 差,指向路由★ —— 而那是唯一没法在应用层代码里解决的。
BoltTx 在这里的位置
我们做的是"端点到验证节点"这一段。不做索引,不做流式推送,不做解析历史。
提交走我们自建的四区域投递节点,带 SWQoS 路由,不暴露公共 mempool —— 交易在落块之前,传输途中观察不到。你在本地签名,我们不托管资金、不代签、不修改交易内容。
tip 带在交易里、从你自己的钱包链上支付,交易失败时跟着一起 revert —— 这是 Solana 原子交易的机制决定的。只有到达链上的交易才计费。
免费领 API key,没有月费:
// 选离你机器人最近的区域
const connection = new Connection("https://la.bolttx.io/?api-key=YOUR_KEY");
常见问题
Solana 上什么算高频交易? 机会在一两个 slot 内就关闭的策略 —— 套利、狙击、清算、报价很紧的做市。核心约束是:在你交易所依据的状态改变之前落块。
Solana 够快到能做 HFT 吗? 按 slot 粒度运作的策略,够。从传统场所照搬过来的微秒级策略,不够 —— 因为链按 slot 量化,亚 slot 的速度优势转化不出来。
Solana HFT 机器人该用 Rust 还是 TypeScript? 原始速度很少是差异所在,因为计算时间相对网络时间可以忽略。Rust 的优势在 GC 停顿和尾部可预测性。如果你的 TypeScript 机器人已经稳定两个 slot 内落块,重写不会改变成交率。
为什么把机器人做得更快,成交率没提升? 因为 Solana 按离散的 slot 收录交易。省毫秒只有在把你推过 slot 边界时才有意义。测 slot 距离而不是毫秒,才能看出一个优化到底有没有起作用。
Solana HFT 最大的延迟因素是什么? 通常是网络地理位置,以及从你的端点到出块方那一段。签名、序列化、构造指令加起来只占总时间中可以忽略的一部分。
怎么降低 Solana HFT 的交易延迟? 复用连接、提交到就近端点、后台刷新 blockhash 让"决定到提交"之间不碰网络、以及按实时行情算 priority fee。
Solana 上做 colocation 有用吗? 离你的提交端点近有用。再往上,验证节点的排程和质押权重的接收比例,比物理上贴近某一台机器更重要。
HFT 机器人的 slot 差多少算好? 稳定在两个 slot 内是个合理目标。一致性比中位数更重要 —— 一条偶尔有长尾的路径,恰好会错过那些竞争激烈的机会。
高吞吐下怎么应对限流?
用 getMultipleAccounts 批量取代循环调 getAccountInfo、能用订阅的地方别轮询、并给程序订阅加服务端过滤。读取量和发送表现是两个独立约束。
HFT 该用 durable nonce 吗? 只有当交易需要在约 150 个区块的 blockhash 窗口之外仍然有效时才需要。对快进快出的交易来说,一个后台刷新的 recent blockhash 更简单,而且不需要多一个账户和一条指令。
我自己的交易会互相竞争吗? 会。两笔在同一个 slot 里写同一个账户的交易会互相串行化。量一大这是个真实效应,所以在策略允许时应该把写入分散到不同账户上。
垃圾回收真的会让我丢单吗? 偶尔会,而且在最糟的时刻。一次 major GC 停顿可能超过一个 slot,而它最可能发生在高负载、队列最深的时候 —— 那正是机会扎堆的时候。
该测什么才知道我的 HFT 配置有没有问题? 从提交到落块的 slot 距离,按拥堵分桶,当作分布来看。平均值会盖住尾部,而延迟敏感的策略恰恰活在尾部。
Solana 上做市可行吗? 报价更新和撤单都是链上交易,受和其它交易一样的上链约束。经济性取决于"你需要多频繁重新报价"对上"那些更新有多可靠地落块"。
为什么我的机器人回测好、实盘差? 回测通常假设你拿到了你看到的价格。实盘上,你的交易晚一个或多个 slot 落在已经改变的状态上,而拥堵时这个差距恰好在机会出现时拉大。
compute unit limit 对 HFT 有多重要? 它不影响速度,但估低了会让本来能落块的交易失败,而用默认值又会按一个高得多的数字计费。开发阶段模拟一次,设在真实用量略高处。