
jev-trader 的作用
jev-trader 是一个逐区块运行的交易机器人,用于 Monad 上 Kuru 的 MON-USDC 订单簿。每产生一个新区块,它都会读取实时订单簿,询问模型是买入还是卖出,并在选定的一侧创建只挂单限价订单意图。
订单价格位于当前最优价内侧 QUOTE_INSIDE_TICKS 个 tick。如果价差过窄,价格会被限制在最优价。机器人会取消此前仍在挂单的订单,并通过一次 Kuru batchUpdate 发布替换订单。
由于订单为只挂单订单,其设计目的是留在订单簿中,而不是立即成交。当其他参与者提交穿过该价格的吃单时,订单才会成交。这样策略可以赚取价差,而不是支付价差,但成交、库存、Gas 和市场风险仍然存在。
主要功能
- 每个 Monad 区块做出一次决策:机器人约每 300 毫秒处理最新区块。
- 实时 Kuru 订单簿数据:自定义读取器解码
getL2Book,并将 AMM 金库合并到订单簿中。 - 两种模型选项:默认模拟模型使用动量启发式方法,而
MODEL=jev会启用 TypeSafe Jev 模型。 - 安全的试运行行为:未设置
PRIVATE_KEY时,机器人使用真实订单簿和真实市场成交记录,但会模拟自身的订单和成交。 - 只挂单订单替换:每次更新都会取消已知的挂单,并在一次批处理中提交新的只挂单订单。
- 仓位与盈亏跟踪:事件会报告仓位、已实现和未实现结果、Gas 成本、成交次数及其他汇总数据。
- HTTP 和 SSE 监控:小型 Bun 服务器提供最新快照、最多 1,000 条历史区块事件以及实时事件流。
- 异步确认:实时交易会在主流程中提交,而收据及相关检查会在之后处理。
项目架构
源代码树将区块接收、市场访问、模型评估、交易逻辑和监控分开:
src/config.ts加载环境配置。src/chain.ts接收 WebSocketnewHeads更新,使用轮询作为后备机制,并且只保留最新区块。src/book.ts通过一次eth_call读取并解码订单簿。src/market.ts处理 Kuru 订单簿读取、保证金存入、本地 nonce、批量更新和异步确认。src/model.ts定义模型接口,并提供JevModel和MockModel。src/trader.ts运行交易循环并跟踪仓位与盈亏。src/server.ts使用Bun.serve提供快照、历史记录和服务器发送事件。
安装与设置
1. 准备环境文件
在仓库目录中复制示例配置:
cp .env.example .env启动应用前,请检查生成的 .env 文件。最重要的行为取决于是否设置了 PRIVATE_KEY。
首次运行时请不要设置
PRIVATE_KEY。应用会基于真实市场数据执行试运行,而不会广播自身的订单。
2. 安装依赖
项目使用 Bun 管理依赖并执行程序:
bun install3. 启动交易机器人
bun run start在默认配置下,模拟动量模型会作为推理替代方案运行。随后,服务器会通过 HTTP 端点提供交易机器人状态和事件流。
选择决策模型
使用默认模拟模型
默认的 mock 模型适合在不调用 Jev 的情况下测试完整流程。它使用动量启发式方法做出决策,让你能够验证订单簿读取、模拟订单、成交、记账和事件流。
启用 Jev 模型
要使用 Jev,请在 .env 中配置以下值:
MODEL=jev
TYPESAFE_AI_API_KEY=your_api_key每个区块都会向选定的模型询问 HORIZON_BLOCKS 范围内的预期变动。其默认值为 100 个区块,根据项目 README 约为 30 秒。
正常决策只有 buy 或 sell。hold 并不代表第三种模型策略:它只会在模型错过当前区块截止时间时出现。在这种情况下,decision.late 为 true,该区块不会发布订单。
安全地运行试运行模式
未配置 PRIVATE_KEY 时,jev-trader 不会提交实时交易。它仍会读取真实的 Kuru 订单簿,并生成真实的模型决策。
模拟报价的 status: "sim"。它会挂单一个区块,只有当真实市场成交记录穿过其价格时,才会被视为已成交。生成的成交记录包含 simulated: true。
此模式适合验证:
- 订单簿更新是否稳定到达。
- 模型延迟是否保持在区块时间预算内。
- 报价可能成交的频率。
- 仓位和盈亏如何根据模拟规则变化。
- 仪表板或其他 SSE 客户端是否能正确处理每种事件类型。
检查 HTTP 端点
一个使用模拟模型部署的试运行实例位于 https://jev-trader-production.up.railway.app。
阅读最新快照
根端点会报告当前模型、钱包、试运行状态以及最近的区块事件:
curl https://jev-trader-production.up.railway.app/读取近期历史记录
历史记录端点会返回最近 1,000 个区块事件:
curl https://jev-trader-production.up.railway.app/history关注实时 SSE 流
使用 curl -N 保持连接开启,并在事件到达时显示它们:
curl -N https://jev-trader-production.up.railway.app/events客户端连接时,数据流会发送一个 snapshot 事件,随后每处理一个区块就发送一个 block 事件。实时交易回执会生成 quote 更新,检测到的成交会生成 fill 事件。
了解区块事件
区块事件汇总了市场状态、模型决策、新的报价意图、挂单流动性、持仓信息以及累计总数。简化示例如下:
{
"block": 105488269,
"mid": 0.022636,
"bestBid": 0.022628,
"bestAsk": 0.022644,
"spreadBps": 7.07,
"decision": {
"action": "buy",
"probabilities": { "buy": 0.77, "sell": 0.23, "hold": 0 },
"upIn10": 0.77,
"latencyMs": 81,
"late": false
},
"quote": {
"side": "buy",
"price": 0.022629,
"size": 200,
"status": "sent",
"capped": false
}
}mid、bestBid和bestAsk描述观测到的市场状态。spreadBps以基点表示当前价差。decision.action是模型选择的方向。decision.probabilities保留模型对买入和卖出的概率。upIn10等于买入概率。decision.latencyMs报告模型评估延迟。quote描述本区块尝试提交的订单。quote.cancel标识需要取消的既有挂单。resting记录更新后机器人已知的买单和卖单数量。position报告方向、数量、入场价格和未实现结果。totals累计区块数、决策数、报价数、成交数、反转数、延迟区块数、模型成本、燃料费和盈亏。
持仓与资金上限
如果持仓上限阻止请求的方向,或实时保证金资金不足以执行该方向,报价就会被放置在相反方向。在这种情况下,quote.capped 为 true。原始模型概率保持不变,因此消费者可以区分模型的判断与实际报价的方向。
了解实时报价确认
实时发送采用触发后不等待回执的方式,因此交易循环无需等待回执。初始区块事件包含的是一个 意图,其 status: "sent"。
报告中的 gasMon 由燃料上限乘以最近已知的基础费加优先费计算得出。项目说明 Monad 会收取燃料上限对应的费用,因此无论交易成功还是回滚,这都会被视为实际成本。
回执通常会在一两个区块后到达,并生成单独的报价事件:
event: quote
data: {
"block": 105488269,
"quote": {
"status": "placed",
"orderId": 100295812,
"gasMon": 0.0357
}
}实时报价可能进入以下状态之一:
placed:交易成功,并包含订单 ID。reverted:订单簿在交易纳入前穿过了提交价格,或者请求取消的目标订单已经成交。lost:10 个区块后仍未找到回执。
了解成交事件
成交不会发生在机器人自己的下单交易中,而是发生在另一位交易者的吃单击中挂单时。机器人通过同一个 eth_getLogs 轮询流程检测相应的 Trade 日志,该流程也为模型提供市场信息。
event: fill
data: {
"block": 105488271,
"fill": {
"side": "buy",
"size": 200,
"price": 0.022629,
"txHash": "0x…",
"orderId": 100295812,
"simulated": false
}
}成交的 txHash 属于吃单交易。处理成交时,数据流还会反映更新后的持仓数据、已实现盈亏以及累计成交总数。
切换到实时交易
设置 PRIVATE_KEY 会将应用从试运行行为切换为实时交易提交。在此之前,请确认环境配置、保证金资金、持仓限制、交易规模、报价下单、费用上限和 RPC 可靠性。
实时模式会提交真实的 Kuru 只做挂单订单,并产生真实燃料成本。启用已注资的密钥前,请先在试运行模式下测试完整流程,并使用保守的限制。
项目描述的主要策略相关设置包括:
TRADE_SIZE_MON:订单规模。QUOTE_INSIDE_TICKS:相对于最优价的下单位置。HORIZON_BLOCKS:模型的预测时间范围。MAX_FEE_GWEI:类型 2 交易的费用上限。READ_RPC_URL:读取订单簿。RPC_URL:提交交易。
机器人如何满足 300 毫秒预算
热路径的设计严格基于两次 RPC 往返:
- 通过
READ_RPC_URL执行一次eth_call,读取当前订单簿。 - 通过
RPC_URL执行一次eth_sendRawTransaction,提交签名后的更新,并在交易被接受后返回。
该循环会有意避开多项可能成本高昂的操作:
- 不针对每个区块调用
eth_estimateGas;gas 上限采用硬编码,或在启动时统一计算一次。 - 不调用
eth_sendRawTransactionSync,因为该方法会一直等待交易被提议。 - 热路径中不查询 gas 价格;交易使用静态的 type-2 费用设置,优先费用为 2 gwei,并受
MAX_FEE_GWEI上限约束。 - 决策循环中不等待收据。
- 不执行可能延误下单的内联 vault 或费用检查。
README 报告的干运行模拟模型基准显示:盘口读取的 p50 约为 18 ms,完整循环的 p50 约为 100 ms,其中约 80 ms 归因于模拟推理替身。
验证与基准测试
对自定义盘口读取器进行基准测试
运行随附的基准测试,将自定义读取器与 SDK 在准确性和延迟方面进行比较:
bun run scripts/bench-read.ts离线验证交易编码
干编码脚本会在不广播交易的情况下,对买入和卖出交易进行签名,然后检查生成的 calldata 是否与 SDK 一致:
bun run scripts/dry-encode.ts在启用实时模式前,这项检查尤其有用,因为面向性能的市场模块使用手动编码的 batchUpdate calldata。
高级运行提示
- 关注延迟决策:跟踪
decision.late和totals.lateBlocks。模型响应延迟意味着该区块不会提交订单。 - 区分意图与确认:不要将
status: "sent"视为订单已下达。应等待后续包含placed、reverted或lost的quote事件。 - 跟踪触及上限的报价:当
capped为 true 时,将提交的方向与未改变的模型概率进行比较,以了解执行结果为何偏离信号。 - 使用 SSE 快照:新的监控客户端应从第一个
snapshot事件初始化,然后应用后续的区块、报价和成交更新。 - 测量两条 RPC 路径:读取缓慢会延误决策,而提交缓慢会降低报价在市场变动前成交的机会。
- 考虑实际收取的 gas 上限:交易即使回退,仍可能产生显著成本,因此应监控
gasMon、gasUsd和回退总数,而不能只评估成交情况。 - 通过干运行输出进行验证:模拟成交取决于真实成交价穿过订单价格。与立即成交假设相比,干运行模式更贴近实际,但仍与真实排队位置和执行结果存在差异。
结论
jev-trader 结合了实时 Kuru 盘口读取器、模拟或 Jev 决策模型、经过优化的 Monad 交易提交、持仓记账以及 SSE 监控服务。在不设置 PRIVATE_KEY 的情况下启动,检查快照和事件流,对关键路径进行基准测试,并在考虑实时执行前验证编码。在生产环境中,应综合监控确认状态、延迟区块、上限、回退、库存、P&L 和 gas 成本。
