Chuyển đến nội dung chính
Hướng dẫn AI

Xây dựng và vận hành nhà tạo lập thị trường Monad theo từng block với jev-trader

Tìm hiểu cách cài đặt jev-trader, chạy an toàn ở chế độ chạy thử, kết nối mô hình Jev, kiểm tra các luồng HTTP và SSE, diễn giải báo giá và giao dịch khớp, đồng thời hiểu các tối ưu hóa độ trễ giúp vòng lặp giao dịch hoạt động trong thời gian block khoảng 300 ms của Monad.

Xây dựng và vận hành nhà tạo lập thị trường Monad theo từng block với jev-trader

jev-trader hoạt động như thế nào

jev-trader là bot giao dịch theo từng block dành cho sổ lệnh Kuru MON-USDC trên Monad. Ở mỗi block mới, bot đọc sổ lệnh hiện tại, yêu cầu một mô hình quyết định mua hoặc bán, rồi tạo một lệnh giới hạn chỉ đặt lệnh (post-only) ở phía đã chọn.

Lệnh được định giá vào bên trong giá khớp hiện tại QUOTE_INSIDE_TICKS tick. Nếu chênh lệch giá quá hẹp, giá sẽ được giới hạn ở mức giá khớp. Bot hủy các lệnh đang chờ trước đó và đăng lệnh thay thế trong một lần gọi Kuru batchUpdate.

Vì là lệnh chỉ đặt lệnh, lệnh được thiết kế để nằm trên sổ lệnh thay vì khớp ngay lập tức. Lệnh được khớp khi một người tham gia khác gửi lệnh taker vượt qua mức giá của lệnh đó. Điều này cho phép chiến lược kiếm chênh lệch giá thay vì trả chênh lệch giá, dù các rủi ro về khớp lệnh, tồn kho, gas và thị trường vẫn áp dụng.

Tính năng chính

  • Một quyết định cho mỗi block Monad: Bot xử lý block mới nhất khoảng 300 ms một lần.
  • Dữ liệu sổ lệnh Kuru theo thời gian thực: Một trình đọc tùy chỉnh giải mã getL2Book và hợp nhất vault AMM vào sổ lệnh.
  • Hai tùy chọn mô hình: Mô hình mock mặc định sử dụng heuristic động lượng, trong khi MODEL=jev bật mô hình Jev TypeSafe.
  • Chế độ chạy thử an toàn: Khi không có PRIVATE_KEY, bot sử dụng sổ lệnh và giao dịch thị trường thực nhưng mô phỏng các lệnh và lần khớp của chính nó.
  • Thay thế lệnh chỉ đặt lệnh: Mỗi lần cập nhật, bot hủy các lệnh đang chờ đã biết và gửi một lệnh chỉ đặt lệnh mới trong cùng một batch.
  • Theo dõi vị thế và P&L: Các sự kiện báo cáo vị thế, kết quả đã thực hiện và chưa thực hiện, chi phí gas, số lần khớp và các tổng số khác.
  • Giám sát HTTP và SSE: Một máy chủ Bun nhỏ cung cấp snapshot mới nhất, tối đa 1.000 sự kiện block trong lịch sử và luồng sự kiện trực tiếp.
  • Xác nhận bất đồng bộ: Giao dịch trực tiếp được gửi trên luồng xử lý chính, còn biên lai và các kiểm tra liên quan được xử lý sau.

Kiến trúc dự án

Cây mã nguồn tách biệt việc tiếp nhận block, truy cập thị trường, đánh giá mô hình, logic giao dịch và giám sát:

  • src/config.ts tải cấu hình môi trường.
  • src/chain.ts nhận các cập nhật newHeads qua WebSocket, sử dụng polling dự phòng và chỉ giữ lại block mới nhất.
  • src/book.ts đọc và giải mã sổ lệnh bằng một eth_call.
  • src/market.ts xử lý việc đọc sổ lệnh Kuru, nạp tiền ký quỹ, nonce cục bộ, cập nhật theo batch và xác nhận bất đồng bộ.
  • src/model.ts định nghĩa giao diện mô hình và cung cấp JevModel cùng MockModel.
  • src/trader.ts chạy vòng lặp giao dịch và theo dõi vị thế cùng P&L.
  • src/server.ts cung cấp snapshot, lịch sử và Server-Sent Events bằng Bun.serve.

Cài đặt và thiết lập

1. Chuẩn bị tệp môi trường

Từ thư mục repository, sao chép cấu hình mẫu:

cp .env.example .env

Kiểm tra tệp .env vừa tạo trước khi khởi động ứng dụng. Hành vi quan trọng nhất được kiểm soát bởi việc PRIVATE_KEY có tồn tại hay không.

Để trống PRIVATE_KEY trong lần chạy đầu tiên. Ứng dụng sẽ chạy thử với dữ liệu thị trường thực mà không phát tán các lệnh của chính nó.

2. Cài đặt các dependency

Dự án sử dụng Bun để quản lý và thực thi dependency:

bun install

3. Khởi động trader

bun run start

Với cấu hình mặc định, mô hình động lượng mock đóng vai trò thay thế cho suy luận. Sau đó, máy chủ cung cấp trạng thái trader và luồng sự kiện thông qua các endpoint HTTP.

Chọn mô hình ra quyết định

Sử dụng mô hình mock mặc định

Mô hình mock mặc định phù hợp để kiểm thử toàn bộ pipeline mà không gọi Jev. Mô hình đưa ra quyết định bằng heuristic động lượng, giúp bạn xác thực việc đọc sổ lệnh, lệnh mô phỏng, lần khớp, hạch toán và truyền sự kiện.

Bật mô hình Jev

Để sử dụng Jev, hãy cấu hình các giá trị sau trong .env:

MODEL=jev
TYPESAFE_AI_API_KEY=your_api_key

Ở mỗi block, mô hình đã chọn được hỏi về biến động dự kiến trong HORIZON_BLOCKS. Giá trị mặc định là 100 block, tương đương khoảng 30 giây theo README của dự án.

Quyết định thông thường là buy hoặc sell. Kết quả hold không đại diện cho chiến lược thứ ba của mô hình: nó chỉ xuất hiện khi mô hình bỏ lỡ thời hạn của block hiện tại. Trong trường hợp đó, decision.late là true và không có lệnh nào được đăng cho block đó.

Chạy an toàn ở chế độ chạy thử

Khi PRIVATE_KEY chưa được cấu hình, jev-trader không gửi giao dịch trực tiếp. Bot vẫn đọc sổ lệnh Kuru thực và tạo ra các quyết định thực từ mô hình.

Báo giá mô phỏng có status: "sim". Báo giá nằm trên sổ lệnh trong một block và chỉ được xem là đã khớp khi một giao dịch thị trường thực vượt qua mức giá của nó. Lần khớp kết quả có simulated: true.

Chế độ này hữu ích để kiểm tra:

  • Các cập nhật sổ lệnh có đến ổn định hay không.
  • Độ trễ của mô hình có nằm trong ngân sách của block hay không.
  • Báo giá sẽ được khớp thường xuyên đến mức nào.
  • Vị thế và P&L thay đổi như thế nào theo các quy tắc mô phỏng.
  • Dashboard hoặc một client SSE khác có xử lý đúng mọi loại sự kiện hay không.

Kiểm tra các endpoint HTTP

Một instance chạy thử đã triển khai sử dụng mô hình mock hiện có tại https://jev-trader-production.up.railway.app.

Đọc ảnh chụp mới nhất

Endpoint gốc báo cáo mô hình đang hoạt động, số dư ví, trạng thái chạy thử và sự kiện block gần nhất:

curl https://jev-trader-production.up.railway.app/

Đọc lịch sử gần đây

Endpoint lịch sử trả về 1.000 sự kiện block gần nhất:

curl https://jev-trader-production.up.railway.app/history

Theo dõi luồng SSE trực tiếp

Sử dụng curl -N để giữ kết nối mở và hiển thị các sự kiện khi chúng đến:

curl -N https://jev-trader-production.up.railway.app/events

Luồng gửi một sự kiện snapshot khi client kết nối, sau đó là một sự kiện block cho mỗi block được xử lý. Biên nhận giao dịch trực tiếp tạo ra các bản cập nhật quote, còn các lần khớp lệnh được phát hiện tạo ra sự kiện fill.

Tìm hiểu sự kiện block

Sự kiện block kết hợp trạng thái thị trường, quyết định của mô hình, ý định báo giá mới, thanh khoản đang chờ, thông tin vị thế và các tổng số lũy kế. Ví dụ rút gọn:

{
  "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 và bestAsk mô tả thị trường được quan sát.
  • spreadBps biểu thị spread hiện tại theo điểm cơ bản.
  • decision.action là hướng được mô hình lựa chọn.
  • decision.probabilities giữ nguyên xác suất mua và bán của mô hình.
  • upIn10 bằng với xác suất mua.
  • decision.latencyMs báo cáo độ trễ đánh giá mô hình.
  • quote mô tả lệnh được thử trong block này.
  • quote.cancel xác định các lệnh đang chờ trước đó được đưa vào để hủy.
  • resting ghi nhận quy mô bid và ask mà bot biết sau khi cập nhật.
  • position báo cáo hướng, quy mô, giá vào lệnh và kết quả chưa thực hiện.
  • totals cộng dồn số block, quyết định, báo giá, lần khớp, lần đảo chiều, block trễ, chi phí mô hình, gas và P&L.

Giới hạn vị thế và nguồn vốn

Nếu giới hạn vị thế ngăn cản phía được yêu cầu hoặc tiền ký quỹ trực tiếp không đủ, báo giá sẽ được đặt ở phía đối diện. Trong trường hợp này, quote.capped là true. Xác suất ban đầu của mô hình vẫn được giữ nguyên, cho phép các bên sử dụng phân biệt dự đoán của mô hình với phía thực sự được báo giá.

Tìm hiểu xác nhận báo giá trực tiếp

Các lệnh gửi trực tiếp được thực hiện mà không chờ kết quả để vòng lặp giao dịch không phải đợi biên nhận. Vì vậy, sự kiện block ban đầu chứa một ý định với status: "sent".

gasMon được báo cáo được tính bằng giới hạn gas nhân với tổng phí cơ sở và phí ưu tiên được biết gần nhất. Dự án ghi chú rằng Monad tính phí theo giới hạn gas, vì vậy đây được xem là chi phí thực tế dù giao dịch thành công hay bị hoàn nguyên.

Biên nhận thường đến sau một hoặc hai block và tạo ra một sự kiện quote riêng:

event: quote
data: {
  "block": 105488269,
  "quote": {
    "status": "placed",
    "orderId": 100295812,
    "gasMon": 0.0357
  }
}

Một báo giá trực tiếp có thể chuyển sang một trong các trạng thái sau:

  • placed: Giao dịch thành công và bao gồm mã lệnh.
  • reverted: Sổ lệnh đã di chuyển qua mức giá được gửi trước khi được đưa vào block, hoặc yêu cầu hủy nhắm đến một lệnh đã được khớp.
  • lost: Không tìm thấy biên nhận sau 10 block.

Tìm hiểu sự kiện khớp lệnh

Các lần khớp không xảy ra trong giao dịch đặt lệnh của chính bot. Chúng xảy ra khi lệnh taker của một nhà giao dịch khác khớp vào lệnh đang chờ. Bot phát hiện log Trade tương ứng thông qua cùng quy trình polling eth_getLogs cung cấp thông tin thị trường cho mô hình.

event: fill
data: {
  "block": 105488271,
  "fill": {
    "side": "buy",
    "size": 200,
    "price": 0.022629,
    "txHash": "0x…",
    "orderId": 100295812,
    "simulated": false
  }
}

txHash của lần khớp thuộc về giao dịch taker. Khi một lần khớp được xử lý, luồng cũng phản ánh dữ liệu vị thế đã cập nhật, P&L đã thực hiện và tổng số lần khớp lũy kế.

Chuyển sang giao dịch trực tiếp

Thiết lập PRIVATE_KEY sẽ chuyển ứng dụng từ chế độ chạy thử sang gửi giao dịch trực tiếp. Trước khi thực hiện, hãy xác minh cấu hình môi trường, nguồn tiền ký quỹ, giới hạn vị thế, quy mô giao dịch, việc đặt báo giá, giới hạn phí và độ tin cậy của RPC.

Chế độ trực tiếp gửi các lệnh Kuru post-only thực và phát sinh chi phí gas thực. Trước tiên hãy kiểm thử toàn bộ quy trình ở chế độ chạy thử và sử dụng các giới hạn thận trọng khi bật khóa đã được cấp vốn.

Các cài đặt chính liên quan đến chiến lược được dự án mô tả gồm:

  • TRADE_SIZE_MON cho quy mô lệnh.
  • QUOTE_INSIDE_TICKS cho vị trí đặt lệnh so với mức giá hiện hành.
  • HORIZON_BLOCKS cho khoảng dự đoán của mô hình.
  • MAX_FEE_GWEI cho giới hạn phí giao dịch type-2.
  • READ_RPC_URL cho việc đọc sổ lệnh.
  • RPC_URL cho việc gửi giao dịch.

Cách bot đáp ứng ngân sách 300 ms

Đường xử lý chính được thiết kế với đúng hai lượt khứ hồi RPC:

  1. Một lệnh eth_call đọc sổ lệnh hiện tại thông qua READ_RPC_URL.
  2. Một lệnh eth_sendRawTransaction gửi bản cập nhật đã ký thông qua RPC_URL và trả về ngay khi được chấp nhận.

Vòng lặp này cố ý tránh một số thao tác có thể tốn nhiều chi phí:

  • Không gọi eth_estimateGas cho từng block; giới hạn gas được cố định hoặc tính một lần khi khởi động.
  • Không dùng eth_sendRawTransactionSync, vốn chờ đến khi giao dịch được đề xuất.
  • Không tra cứu giá gas trên đường xử lý chính; các giao dịch sử dụng thiết lập phí tĩnh type-2 với phí ưu tiên 2 gwei và giới hạn MAX_FEE_GWEI.
  • Không chờ receipt trong vòng lặp quyết định.
  • Không kiểm tra vault hoặc phí nội tuyến, vì có thể làm chậm lệnh.

README ghi nhận benchmark dry-run với mô hình giả lập đạt khoảng 18 ms p50 cho lần đọc sổ lệnh và 100 ms p50 cho toàn bộ vòng lặp, trong đó khoảng 80 ms đến từ phần thay thế suy luận giả lập.

Kiểm thử và benchmark

Benchmark trình đọc sổ lệnh tùy chỉnh

Chạy benchmark đi kèm để so sánh trình đọc tùy chỉnh với SDK về độ chính xác và độ trễ:

bun run scripts/bench-read.ts

Xác minh mã hóa giao dịch ngoại tuyến

Script mã hóa dry-run ký một lệnh mua và một lệnh bán mà không phát chúng, sau đó kiểm tra calldata được tạo có khớp với SDK hay không:

bun run scripts/dry-encode.ts

Điều này đặc biệt hữu ích trước khi bật chế độ live, vì module thị trường tối ưu hóa sử dụng calldata batchUpdate được mã hóa thủ công.

Mẹo vận hành nâng cao

  • Theo dõi các quyết định trễ: Theo dõi decision.late và totals.lateBlocks. Phản hồi mô hình đến trễ nghĩa là không có lệnh nào được đăng cho block đó.
  • Tách ý định khỏi xác nhận: Không xem status: "sent" là một lệnh đã được đặt. Hãy chờ sự kiện quote đến sau với trạng thái placed, reverted hoặc lost.
  • Theo dõi các báo giá bị giới hạn: Khi capped là true, hãy so sánh phía đã gửi với các xác suất mô hình không thay đổi để hiểu vì sao quá trình thực thi lệch khỏi tín hiệu.
  • Sử dụng snapshot SSE: Các client giám sát mới nên khởi tạo từ sự kiện snapshot đầu tiên, sau đó áp dụng các cập nhật block, quote và fill tiếp theo.
  • Đo lường cả hai đường RPC: Việc đọc chậm làm chậm quyết định, trong khi việc gửi chậm làm giảm khả năng quote được thực hiện trước khi thị trường biến động.
  • Tính đến giới hạn gas bị tính phí: Một giao dịch bị revert vẫn có thể phát sinh chi phí đáng kể, vì vậy hãy theo dõi gasMon, gasUsd và tổng số lần revert thay vì chỉ đánh giá các fill.
  • Xác thực bằng bản in dry-run: Các fill mô phỏng phụ thuộc vào các giao dịch thực tế chạm mức giá lệnh, khiến chế độ dry-run thực tế hơn giả định khớp lệnh ngay lập tức, nhưng vẫn khác với vị trí thực tế trong hàng đợi và quá trình thực thi.

Kết luận

jev-trader kết hợp trình đọc sổ lệnh Kuru trực tiếp, mô hình quyết định giả lập hoặc Jev, cơ chế gửi giao dịch Monad được tối ưu hóa, tính toán vị thế và dịch vụ giám sát SSE. Hãy khởi động mà không có PRIVATE_KEY, kiểm tra snapshot và luồng sự kiện, benchmark đường xử lý quan trọng, đồng thời xác minh việc mã hóa trước khi cân nhắc thực thi trực tiếp. Trong môi trường production, hãy cùng lúc theo dõi xác nhận, các block trễ, giới hạn, giao dịch revert, tồn kho, P&L và chi phí gas.