StreamL2BookDiff gRPC Method
请注意,此方法的计费是根据数据消耗量计算的,按 0.0165 MB = 10 个 API 积分 进行计费。
参数
coins
repeated string
正在加载...
n_levels
uint32
正在加载...
n_sig_figs
uint32
正在加载...
尾数
uint64
正在加载...
skip_initial_snapshot
bool
正在加载...
退货
流
stream<L2BookDiffUpdate>
正在加载...
时间
uint64
正在加载...
高度
uint64
正在加载...
snapshot
bool
正在加载...
diffs
array<L2CoinDiff>
正在加载...
请求
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2包 main34import (5"上下文"6“flag”7"fmt"8"io"9“日志”10"数学"11“字符串”12“时间”1314"google.golang.grpc"15"google.golang.grpc"16"google.golang.grpc"17"google.golang.grpc"18"google.golang.grpc"1920pb "hyperliquid"21)2223const (24grpcEndpoint = "endpoint.mainnet.quiknode.pro:10000"25authToken = "您的认证令牌"26maxRetries = 1027baseDelay = 2 * time.秒28)2930func streamL2BookDiff(coins []string, nLevels uint32) error {31fmt.PrintlnPrintln字符串.重复("=", 60))32fmt.Printf("Streaming L2 Book Diffs for %s\n", strings.Join(coins, ", "))33fmt.Printf("水位:%d\n", nLevels)34fmt.Println("自动重连:true")35fmt.PrintlnPrintln字符串.重复("=", 60) + "\n")3637retryCount := 03839// Track last seq per coin for gap detection40lastSeq := make(map[string]uint64)4142for retryCount < maxRetries {43creds := 凭证.NewClientTLSFromCert(nil, "")44conn, err := grpc.Dial(grpcEndpoint,45grpc.WithTransportCredentials(creds),46grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))47如果 err != nil {48返回 fmt.Errorf("连接失败:%w", err)49}5051client := pb.NewOrderBookStreamingClient(conn)52ctx := 元数据.AppendToOutgoingContext(context.背景(), “x-token”, authToken)5354request := &pb.L2BookDiffRequest{55Coins: coins,56NLevels: nLevels,57}5859if retryCount > 0 {60fmt.Printf("\n🔄 正在重新连接(尝试 %d/%d)...\n", retryCount+1, maxRetries)61} else {62fmt.Printf("正在连接到 %s...\n", grpcEndpoint)63}6465stream, err := client.StreamL2BookDiff(ctx, request)66如果 err != nil {67conn.关闭()68返回 fmt.Errorf("启动流失败:%w", err)69}7071msgCount := 072shouldRetry := false7374用于 {75更新, err := 流.Recv()76如果 err == io.EOF {77换行78}79如果 err != nil {80st, 好的 := 状态.FromError(err)81如果 好的 && st.代码() == 代码.DataLoss {82fmt.Printf("\n⚠️ 服务器已重新初始化:%s\n", st.消息())83retryCount++84if retryCount < maxRetries {85延迟 := baseDelay * time.Duration(数学.Pow(2, float64(重试次数-1)))86fmt.Printf("⏳ 等待 %v 后再重新连接……\n", 延迟)87时间.睡眠(延迟)88shouldRetry = true89换行90} else {91fmt.Printf("\n❌ 达到最大重试次数 (%d)。放弃。\n", maxRetries)92conn.关闭()93返回 nil94}95}96conn.关闭()97返回 fmt.Errorf("流错误:%w", err)98}99100msgCount++101如果 msgCount == 1 {102fmt.Println("✓ First L2 diff update received!\n")103retryCount = 0 // 成功后重置104}105106// Display diff update107fmt.Println("\n" + 字符串.重复("─", 60))108snapshotLabel := ""109if update.Snapshot {110snapshotLabel = " | SNAPSHOT"111}112fmt.Printf("Block: %d | Time: %d%s\n", update.Height, update.Time, snapshotLabel)113fmt.PrintlnPrintln字符串.重复("─", 60))114115for _, diff := range update.Diffs {116// Check seq continuity per coin117if prev, ok := lastSeq[diff.Coin]; ok && diff.PrevSeq != 0 && diff.PrevSeq != prev {118fmt.Printf(" ⚠️ Sequence gap for %s: expected prev_seq %d, got %d\n", diff.Coin, prev, diff.PrevSeq)119}120lastSeq[diff.Coin] = diff.Seq121122label := "DIFF"123if diff.Snapshot {124label = "SNAPSHOT"125}126fmt.Printf("\n %s [%s] seq: %d (prev: %d)\n", diff.Coin, label, diff.Seq, diff.PrevSeq)127128// Changed ask levels (sz "0" means level removed)129for _, level := range diff.Asks {130action := "SET"131if level.Sz == "0" {132action = "REMOVE"133}134fmt.Printf(" ASK %-6s %12s | %12s\n", action, level.Px, level.Sz)135}136137// Changed bid levels138for _, level := range diff.Bids {139action := "SET"140if level.Sz == "0" {141action = "REMOVE"142}143fmt.Printf(" BID %-6s %12s | %12s\n", action, level.Px, level.Sz)144}145}146147fmt.Printf("\n 已接收消息:%d\n", msgCount)148}149150conn.关闭()151152如果 !shouldRetry {153换行154}155}156157返回 nil158}159160func main() {161coinsFlag := flag.String("coins", "BTC", "Comma-separated coin symbols to stream (e.g., BTC,ETH)")162levels := flag.Uint("levels", 20, "Maximum price levels per side")163164flag.解析()165166coins := strings.Split(*coinsFlag, ",")167168fmt.Println("\n" + 字符串.重复("=", 60))169fmt.Println("Hyperliquid StreamL2BookDiff Example")170fmt.Printf("Endpoint:%s\n", grpcEndpoint)171fmt.PrintlnPrintln字符串.重复("=", 60))172173if err := streamL2BookDiff(coins, uint32(*levels)); err != nil {174log.致命(err)175}176}177
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2包 main34import (5"上下文"6“flag”7"fmt"8"io"9“日志”10"数学"11“字符串”12“时间”1314"google.golang.grpc"15"google.golang.grpc"16"google.golang.grpc"17"google.golang.grpc"18"google.golang.grpc"1920pb "hyperliquid"21)2223const (24grpcEndpoint = "endpoint.mainnet.quiknode.pro:10000"25authToken = "您的认证令牌"26maxRetries = 1027baseDelay = 2 * time.秒28)2930func streamL2BookDiff(coins []string, nLevels uint32) error {31fmt.PrintlnPrintln字符串.重复("=", 60))32fmt.Printf("Streaming L2 Book Diffs for %s\n", strings.Join(coins, ", "))33fmt.Printf("水位:%d\n", nLevels)34fmt.Println("自动重连:true")35fmt.PrintlnPrintln字符串.重复("=", 60) + "\n")3637retryCount := 03839// Track last seq per coin for gap detection40lastSeq := make(map[string]uint64)4142for retryCount < maxRetries {43creds := 凭证.NewClientTLSFromCert(nil, "")44conn, err := grpc.Dial(grpcEndpoint,45grpc.WithTransportCredentials(creds),46grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(100*1024*1024)))47如果 err != nil {48返回 fmt.Errorf("连接失败:%w", err)49}5051client := pb.NewOrderBookStreamingClient(conn)52ctx := 元数据.AppendToOutgoingContext(context.背景(), “x-token”, authToken)5354request := &pb.L2BookDiffRequest{55Coins: coins,56NLevels: nLevels,57}5859if retryCount > 0 {60fmt.Printf("\n🔄 正在重新连接(尝试 %d/%d)...\n", retryCount+1, maxRetries)61} else {62fmt.Printf("正在连接到 %s...\n", grpcEndpoint)63}6465stream, err := client.StreamL2BookDiff(ctx, request)66如果 err != nil {67conn.关闭()68返回 fmt.Errorf("启动流失败:%w", err)69}7071msgCount := 072shouldRetry := false7374用于 {75更新, err := 流.Recv()76如果 err == io.EOF {77换行78}79如果 err != nil {80st, 好的 := 状态.FromError(err)81如果 好的 && st.代码() == 代码.DataLoss {82fmt.Printf("\n⚠️ 服务器已重新初始化:%s\n", st.消息())83retryCount++84if retryCount < maxRetries {85延迟 := baseDelay * time.Duration(数学.Pow(2, float64(重试次数-1)))86fmt.Printf("⏳ 等待 %v 后再重新连接……\n", 延迟)87时间.睡眠(延迟)88shouldRetry = true89换行90} else {91fmt.Printf("\n❌ 达到最大重试次数 (%d)。放弃。\n", maxRetries)92conn.关闭()93返回 nil94}95}96conn.关闭()97返回 fmt.Errorf("流错误:%w", err)98}99100msgCount++101如果 msgCount == 1 {102fmt.Println("✓ First L2 diff update received!\n")103retryCount = 0 // 成功后重置104}105106// Display diff update107fmt.Println("\n" + 字符串.重复("─", 60))108snapshotLabel := ""109if update.Snapshot {110snapshotLabel = " | SNAPSHOT"111}112fmt.Printf("Block: %d | Time: %d%s\n", update.Height, update.Time, snapshotLabel)113fmt.PrintlnPrintln字符串.重复("─", 60))114115for _, diff := range update.Diffs {116// Check seq continuity per coin117if prev, ok := lastSeq[diff.Coin]; ok && diff.PrevSeq != 0 && diff.PrevSeq != prev {118fmt.Printf(" ⚠️ Sequence gap for %s: expected prev_seq %d, got %d\n", diff.Coin, prev, diff.PrevSeq)119}120lastSeq[diff.Coin] = diff.Seq121122label := "DIFF"123if diff.Snapshot {124label = "SNAPSHOT"125}126fmt.Printf("\n %s [%s] seq: %d (prev: %d)\n", diff.Coin, label, diff.Seq, diff.PrevSeq)127128// Changed ask levels (sz "0" means level removed)129for _, level := range diff.Asks {130action := "SET"131if level.Sz == "0" {132action = "REMOVE"133}134fmt.Printf(" ASK %-6s %12s | %12s\n", action, level.Px, level.Sz)135}136137// Changed bid levels138for _, level := range diff.Bids {139action := "SET"140if level.Sz == "0" {141action = "REMOVE"142}143fmt.Printf(" BID %-6s %12s | %12s\n", action, level.Px, level.Sz)144}145}146147fmt.Printf("\n 已接收消息:%d\n", msgCount)148}149150conn.关闭()151152如果 !shouldRetry {153换行154}155}156157返回 nil158}159160func main() {161coinsFlag := flag.String("coins", "BTC", "Comma-separated coin symbols to stream (e.g., BTC,ETH)")162levels := flag.Uint("levels", 20, "Maximum price levels per side")163164flag.解析()165166coins := strings.Split(*coinsFlag, ",")167168fmt.Println("\n" + 字符串.重复("=", 60))169fmt.Println("Hyperliquid StreamL2BookDiff Example")170fmt.Printf("Endpoint:%s\n", grpcEndpoint)171fmt.PrintlnPrintln字符串.重复("=", 60))172173if err := streamL2BookDiff(coins, uint32(*levels)); err != nil {174log.致命(err)175}176}177
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2const grpc = require(grpc);3const protoLoader = require(grpc);4const 路径 = require('path');56const GRPC = endpoint.mainnet.quiknode.pro:10000';7const AUTH_TOKEN = '您的认证令牌';8const PROTO_PATH = 路径.join(__dirname, 'proto', 'orderbook.proto');910const 包定义 = protoLoader.loadSync(PROTO_PATH, {11keepCase: true,12多头: 字符串,13枚举: 字符串,14默认值: true,15oneofs: true16});17const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;1819函数 createClient() {20返回 new proto.OrderBookStreaming(21GRPC,22grpc.凭据.createSsl(),23{ 'grpc.max_receive_message_length': 100 * 1024 * 1024 }24);25}2627// Stream incremental L2 book diffs28async function streamL2BookDiff(coins, nLevels = 20, autoReconnect = true, retryCount = 0) {29控制台.日志('='.重复(60));30console.log(`Streaming L2 Book Diffs for ${coins.length > 0 ? coins.join(', ') : 'all coins'}`);31console.log(`Levels: ${nLevels}`);32console.log(`Auto-reconnect: ${autoReconnect}`);33控制台.日志('='.重复(60) + '\n');3435const maxRetries = 10;36const baseDelay = 2000;3738// Track last seq per coin for gap detection39const lastSeq = {};4041while (retryCount < maxRetries) {42const 客户端 = createClient();43const 元数据 = new grpc.元数据();44元数据.添加('x-token', AUTH_TOKEN);4546const 请求 = {47coins: coins,48n_levels: nLevels49};5051试一试 {52如果 (retryCount > 0) {53console.log(`\n🔄 Reconnecting (attempt ${retryCount + 1}/${maxRetries})...`);54} else {55console.log(`Connecting to ${GRPC_ENDPOINT}...`);56}5758let msgCount = 0;59const call = client.StreamL2BookDiff(request, metadata);6061call。在('data', (更新) => {62msgCount++;6364如果 (msgCount === 1) {65console.log('✓ First L2 diff update received!\n');66retryCount = 0; // 成功后重置67}6869控制台.日志('\n' + '─'.重复(60));70console.log(`Block: ${update.height} | Time: ${update.time}${update.snapshot ? ' | SNAPSHOT' : ''}`);71控制台.日志('─'.重复(60));7273(update.diffs || []).forEach(diff => {74// Check seq continuity per coin75if (lastSeq[diff.coin] !== undefined && diff.prev_seq && String(diff.prev_seq) !== String(lastSeq[diff.coin])) {76console.log(` ⚠️ Sequence gap for ${diff.coin}: expected prev_seq ${lastSeq[diff.coin]}, got ${diff.prev_seq}`);77}78lastSeq[diff.coin] = diff.seq;7980const label = diff.snapshot ? 'SNAPSHOT' : 'DIFF';81console.log(`\n ${diff.coin} [${label}] seq: ${diff.seq} (prev: ${diff.prev_seq})`);8283// Changed ask levels (sz "0" means level removed)84(diff.asks || []).forEach(level => {85const action = level.sz === '0' ? 'REMOVE' : 'SET';86console.log(` ASK ${action.padEnd(6)} ${level.px.padStart(12)} | ${level.sz.padStart(12)}`);87});8889// Changed bid levels90(diff.bids || []).forEach(level => {91const action = level.sz === '0' ? 'REMOVE' : 'SET';92console.log(` BID ${action.padEnd(6)} ${level.px.padStart(12)} | ${level.sz.padStart(12)}`);93});94});9596console.log(`\n Messages received: ${msgCount}`);97});9899call。在('error', (err) => {100如果 (err.代码 === grpc.状态.数据丢失 && 自动重连) {101console.log(`\n⚠️ Server reinitialized: ${err.message}`);102retryCount++;103if (retryCount < maxRetries) {104const 延迟 = baseDelay * Math.pow(2, retryCount - 1);105console.log(`⏳ Waiting ${delay / 1000}s before reconnecting...`);106setTimeout(() => streamL2BookDiff(coins, nLevels, autoReconnect, retryCount), delay);107} else {108console.log(`\n❌ Max retries (${maxRetries}) reached. Giving up.`);109}110} else {111控制台.错误('\ngRPC 错误:', err.code, '-', err.message);112}113});114115call。在('end', () => {116控制台.日志('\n流已结束');117});118119// Wait for stream to complete120等待 new Promise((决心) => {121call。在('end', resolve);122call。在('error', resolve);123});124125换行; // 成功后退出重试循环126127} catch (err) {128控制台.错误('错误:', err.message);129换行;130}131}132}133134// Parse command line args135const args = process.argv.slice(2);136const coins = (args.find(a => a.startsWith('--coins='))?.split('=')[1] || 'BTC').split(',');137const 等级 = parseInt(args.find(a => a.startsWith('--levels='))?.split('=')[1]) || 20;138139控制台.日志('\n' + '='.重复(60));140console.log('Hyperliquid StreamL2BookDiff Example');141console.log(`Endpoint: ${GRPC_ENDPOINT}`);142控制台.日志('='.重复(60));143144streamL2BookDiff(coins, levels);145
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2const grpc = require(grpc);3const protoLoader = require(grpc);4const 路径 = require('path');56const GRPC = endpoint.mainnet.quiknode.pro:10000';7const AUTH_TOKEN = '您的认证令牌';8const PROTO_PATH = 路径.join(__dirname, 'proto', 'orderbook.proto');910const 包定义 = protoLoader.loadSync(PROTO_PATH, {11keepCase: true,12多头: 字符串,13枚举: 字符串,14默认值: true,15oneofs: true16});17const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;1819函数 createClient() {20返回 new proto.OrderBookStreaming(21GRPC,22grpc.凭据.createSsl(),23{ 'grpc.max_receive_message_length': 100 * 1024 * 1024 }24);25}2627// Stream incremental L2 book diffs28async function streamL2BookDiff(coins, nLevels = 20, autoReconnect = true, retryCount = 0) {29控制台.日志('='.重复(60));30console.log(`Streaming L2 Book Diffs for ${coins.length > 0 ? coins.join(', ') : 'all coins'}`);31console.log(`Levels: ${nLevels}`);32console.log(`Auto-reconnect: ${autoReconnect}`);33控制台.日志('='.重复(60) + '\n');3435const maxRetries = 10;36const baseDelay = 2000;3738// Track last seq per coin for gap detection39const lastSeq = {};4041while (retryCount < maxRetries) {42const 客户端 = createClient();43const 元数据 = new grpc.元数据();44元数据.添加('x-token', AUTH_TOKEN);4546const 请求 = {47coins: coins,48n_levels: nLevels49};5051试一试 {52如果 (retryCount > 0) {53console.log(`\n🔄 Reconnecting (attempt ${retryCount + 1}/${maxRetries})...`);54} else {55console.log(`Connecting to ${GRPC_ENDPOINT}...`);56}5758let msgCount = 0;59const call = client.StreamL2BookDiff(request, metadata);6061call。在('data', (更新) => {62msgCount++;6364如果 (msgCount === 1) {65console.log('✓ First L2 diff update received!\n');66retryCount = 0; // 成功后重置67}6869控制台.日志('\n' + '─'.重复(60));70console.log(`Block: ${update.height} | Time: ${update.time}${update.snapshot ? ' | SNAPSHOT' : ''}`);71控制台.日志('─'.重复(60));7273(update.diffs || []).forEach(diff => {74// Check seq continuity per coin75if (lastSeq[diff.coin] !== undefined && diff.prev_seq && String(diff.prev_seq) !== String(lastSeq[diff.coin])) {76console.log(` ⚠️ Sequence gap for ${diff.coin}: expected prev_seq ${lastSeq[diff.coin]}, got ${diff.prev_seq}`);77}78lastSeq[diff.coin] = diff.seq;7980const label = diff.snapshot ? 'SNAPSHOT' : 'DIFF';81console.log(`\n ${diff.coin} [${label}] seq: ${diff.seq} (prev: ${diff.prev_seq})`);8283// Changed ask levels (sz "0" means level removed)84(diff.asks || []).forEach(level => {85const action = level.sz === '0' ? 'REMOVE' : 'SET';86console.log(` ASK ${action.padEnd(6)} ${level.px.padStart(12)} | ${level.sz.padStart(12)}`);87});8889// Changed bid levels90(diff.bids || []).forEach(level => {91const action = level.sz === '0' ? 'REMOVE' : 'SET';92console.log(` BID ${action.padEnd(6)} ${level.px.padStart(12)} | ${level.sz.padStart(12)}`);93});94});9596console.log(`\n Messages received: ${msgCount}`);97});9899call。在('error', (err) => {100如果 (err.代码 === grpc.状态.数据丢失 && 自动重连) {101console.log(`\n⚠️ Server reinitialized: ${err.message}`);102retryCount++;103if (retryCount < maxRetries) {104const 延迟 = baseDelay * Math.pow(2, retryCount - 1);105console.log(`⏳ Waiting ${delay / 1000}s before reconnecting...`);106setTimeout(() => streamL2BookDiff(coins, nLevels, autoReconnect, retryCount), delay);107} else {108console.log(`\n❌ Max retries (${maxRetries}) reached. Giving up.`);109}110} else {111控制台.错误('\ngRPC 错误:', err.code, '-', err.message);112}113});114115call。在('end', () => {116控制台.日志('\n流已结束');117});118119// Wait for stream to complete120等待 new Promise((决心) => {121call。在('end', resolve);122call。在('error', resolve);123});124125换行; // 成功后退出重试循环126127} catch (err) {128控制台.错误('错误:', err.message);129换行;130}131}132}133134// Parse command line args135const args = process.argv.slice(2);136const coins = (args.find(a => a.startsWith('--coins='))?.split('=')[1] || 'BTC').split(',');137const 等级 = parseInt(args.find(a => a.startsWith('--levels='))?.split('=')[1]) || 20;138139控制台.日志('\n' + '='.重复(60));140console.log('Hyperliquid StreamL2BookDiff Example');141console.log(`Endpoint: ${GRPC_ENDPOINT}`);142控制台.日志('='.重复(60));143144streamL2BookDiff(coins, levels);145
1#!/usr/bin/env python32"""3StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC45设置:6pip install grpcio grpcio-tools protobuf zstandard7python -mgrpc.protoc -I../../proto --python_out=.grpc. ../../proto/orderbook.proto89用法:10python stream_l2_diff_example.py --coins BTC,ETH --levels 2011"""1213import grpc14import sys15import 时间16import argparse17来自 输入 import Optional1819试一试:20import orderbook_pb2 作为 pb21import orderbook_pb2_grpc 为 pb_grpc22except ImportError:23打印("错误:未生成 Proto 文件。请运行:")24打印(" python -mgrpc.protoc -I../../proto --python_out=.grpc. ../../proto/orderbook.proto")25sys.exit(1)2627# 配置28GRPC= "endpoint.mainnet.quiknode.pro:10000"29AUTH_TOKEN = "您的认证令牌"303132def stream_l2_book_diff(coins: list, n_levels: int = 20, n_sig_figs: Optional[int] = None, mantissa: Optional[int] = None, skip_initial_snapshot: bool = False, auto_reconnect: bool = True):33"""34Stream incremental L2 price-level changes for one or more coins.3536参数:37coins: Symbols to stream (e.g., ["BTC", "ETH"]). Empty list means all coins38n_levels: Maximum number of price levels tracked per side (default 20, max 100)39n_sig_figs:价格分档的有效数字(2-5)40尾数:用于分桶的尾数(1、2 或 5)41skip_initial_snapshot: Skip the initial per-coin snapshot (default False)42auto_reconnect:在发生 DATA_LOSS 错误时自动重新连接(默认值为 True)43"""44打印(f"\n{'='*60}")45print(f"Streaming L2 Book Diffs for {', '.join(coins) if coins else 'all coins'}")46print(f"Levels: {n_levels}")47print(f"Auto-reconnect: {auto_reconnect}")48打印(f"{'='*60}\n")4950retry_count = 051max_retries = 1052base_delay = 25354# Track last seq per coin for gap detection55last_seq = {}5657while retry_count < max_retries:58频道 = grpc.secure_channel(59GRPC,60grpc.ssl_channel_credentials(),61选项=[62(grpc.max_receive_message_length', 100 * 1024 * 1024),63(grpc.keepalive_time_ms', 30000),64]65)66占位符 = pb_grpc.订单流存根(channel)6768# 构建请求69request = pb.L2BookDiffRequest(70coins=coins,71n_levels=n_levels,72skip_initial_snapshot=skip_initial_snapshot73)74如果 n_sig_figs 为 不 None时:75请求.n_sig_figs = n_sig_figs76如果 尾数 是 不 None:77请求.尾数 = 尾数7879msg_count = 08081试一试:82如果 retry_count > 0:83打印(f"\n🔄 正在重新连接(尝试 {重试次数 + 1}/{最大重试次数})...")84else:85打印(f"正在连接到 {GRPC}...")8687for update in stub.StreamL2BookDiff(request, metadata=[('x-token', AUTH_TOKEN)]):88msg_count += 18990如果 msg_count == 1:91print(f"✓ First L2 diff update received!\n")92retry_count = 0 # 连接成功后重置重试计数9394# Display the diff update95打印(f"\n{'─'*60}")96snapshot_label = " | SNAPSHOT" if update.snapshot else ""97print(f"Block: {update.height} | Time: {update.time}{snapshot_label}")98打印(f"{'─'*60}")99100for diff in update.diffs:101# Check seq continuity per coin102if diff.coin in last_seq and diff.prev_seq and diff.prev_seq != last_seq[diff.coin]:103print(f" ⚠️ Sequence gap for {diff.coin}: expected prev_seq {last_seq[diff.coin]}, got {diff.prev_seq}")104last_seq[diff.coin] = diff.seq105106label = "SNAPSHOT" if diff.snapshot else "DIFF"107print(f"\n {diff.coin} [{label}] seq: {diff.seq} (prev: {diff.prev_seq})")108109# Changed ask levels (sz "0" means level removed)110for level in diff.asks:111action = "REMOVE" if level.sz == "0" else "SET"112print(f" ASK {action:<6} {level.px:>12} | {level.sz:>12}")113114# Changed bid levels115for level in diff.bids:116action = "REMOVE" if level.sz == "0" else "SET"117print(f" BID {action:<6} {level.px:>12} | {level.sz:>12}")118119print(f"\n Messages received: {msg_count}")120121除了 grpc。RpcError 作为 e:122如果 e.代码() == grpc.状态码.DATA_LOSS 和 auto_reconnect:123print(f"\n⚠️ Server reinitialized: {e.details()}")124retry_count += 1125if retry_count < max_retries:126延迟 = base_delay * (2 ** (重试次数 - 1)) # 指数退避127打印(f"⏳ 正在等待 {延迟}秒后重新连接...")128时间.sleep(延迟)129频道.关闭()130继续131else:132打印(f"\n❌ 最大重试次数 ({max_retries}) 已达到。放弃。")133换行134else:135print(f"\ngRPC error: {e.code()} - {e.details()}")136换行137除了 KeyboardInterrupt:138print("\nStopping L2 diff stream...")139换行140最后:141频道.关闭()142143# 如果在此处未出现错误,则退出重试循环144换行145146147def main():148parser = argparse.ArgumentParser(description='Stream Hyperliquid incremental L2 order book diffs via gRPC')149parser.add_argument('--coins', default='BTC', help='Comma-separated coin symbols to stream (e.g., BTC,ETH)')150parser.add_argument('--levels', type=int, default=20, help='Maximum price levels per side (default: 20, max: 100)')151解析器.add_argument('--sig-figs', type=int, 默认值=None, 帮助='分桶法中的有效数字(2-5)')152解析器.add_argument('--mantissa', type=int, 默认值=None, 帮助='分桶小数部分(1、2 或 5)')153parser.add_argument('--skip-snapshot', action='store_true', help='Skip the initial per-coin snapshot')154155args = 解析器.parse_args()156coins = [c.strip() for c in args.coins.split(',') if c.strip()]157158打印(f"\n{'='*60}")159print("Hyperliquid StreamL2BookDiff Example")160print(f"Endpoint: {GRPC_ENDPOINT}")161打印(f"{'='*60}")162163试一试:164stream_l2_book_diff(coins, n_levels=args.levels, n_sig_figs=args.sig_figs, mantissa=args.mantissa, skip_initial_snapshot=args.skip_snapshot)165除 异常 作为 e:166print(f"\nError: {e}")167import traceback168回溯.print_exc()169sys.exit(1)170171172如果 __name__ == "__main__":173主()174
1#!/usr/bin/env python32"""3StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC45设置:6pip install grpcio grpcio-tools protobuf zstandard7python -mgrpc.protoc -I../../proto --python_out=.grpc. ../../proto/orderbook.proto89用法:10python stream_l2_diff_example.py --coins BTC,ETH --levels 2011"""1213import grpc14import sys15import 时间16import argparse17来自 输入 import Optional1819试一试:20import orderbook_pb2 作为 pb21import orderbook_pb2_grpc 为 pb_grpc22except ImportError:23打印("错误:未生成 Proto 文件。请运行:")24打印(" python -mgrpc.protoc -I../../proto --python_out=.grpc. ../../proto/orderbook.proto")25sys.exit(1)2627# 配置28GRPC= "endpoint.mainnet.quiknode.pro:10000"29AUTH_TOKEN = "您的认证令牌"303132def stream_l2_book_diff(coins: list, n_levels: int = 20, n_sig_figs: Optional[int] = None, mantissa: Optional[int] = None, skip_initial_snapshot: bool = False, auto_reconnect: bool = True):33"""34Stream incremental L2 price-level changes for one or more coins.3536参数:37coins: Symbols to stream (e.g., ["BTC", "ETH"]). Empty list means all coins38n_levels: Maximum number of price levels tracked per side (default 20, max 100)39n_sig_figs:价格分档的有效数字(2-5)40尾数:用于分桶的尾数(1、2 或 5)41skip_initial_snapshot: Skip the initial per-coin snapshot (default False)42auto_reconnect:在发生 DATA_LOSS 错误时自动重新连接(默认值为 True)43"""44打印(f"\n{'='*60}")45print(f"Streaming L2 Book Diffs for {', '.join(coins) if coins else 'all coins'}")46print(f"Levels: {n_levels}")47print(f"Auto-reconnect: {auto_reconnect}")48打印(f"{'='*60}\n")4950retry_count = 051max_retries = 1052base_delay = 25354# Track last seq per coin for gap detection55last_seq = {}5657while retry_count < max_retries:58频道 = grpc.secure_channel(59GRPC,60grpc.ssl_channel_credentials(),61选项=[62(grpc.max_receive_message_length', 100 * 1024 * 1024),63(grpc.keepalive_time_ms', 30000),64]65)66占位符 = pb_grpc.订单流存根(channel)6768# 构建请求69request = pb.L2BookDiffRequest(70coins=coins,71n_levels=n_levels,72skip_initial_snapshot=skip_initial_snapshot73)74如果 n_sig_figs 为 不 None时:75请求.n_sig_figs = n_sig_figs76如果 尾数 是 不 None:77请求.尾数 = 尾数7879msg_count = 08081试一试:82如果 retry_count > 0:83打印(f"\n🔄 正在重新连接(尝试 {重试次数 + 1}/{最大重试次数})...")84else:85打印(f"正在连接到 {GRPC}...")8687for update in stub.StreamL2BookDiff(request, metadata=[('x-token', AUTH_TOKEN)]):88msg_count += 18990如果 msg_count == 1:91print(f"✓ First L2 diff update received!\n")92retry_count = 0 # 连接成功后重置重试计数9394# Display the diff update95打印(f"\n{'─'*60}")96snapshot_label = " | SNAPSHOT" if update.snapshot else ""97print(f"Block: {update.height} | Time: {update.time}{snapshot_label}")98打印(f"{'─'*60}")99100for diff in update.diffs:101# Check seq continuity per coin102if diff.coin in last_seq and diff.prev_seq and diff.prev_seq != last_seq[diff.coin]:103print(f" ⚠️ Sequence gap for {diff.coin}: expected prev_seq {last_seq[diff.coin]}, got {diff.prev_seq}")104last_seq[diff.coin] = diff.seq105106label = "SNAPSHOT" if diff.snapshot else "DIFF"107print(f"\n {diff.coin} [{label}] seq: {diff.seq} (prev: {diff.prev_seq})")108109# Changed ask levels (sz "0" means level removed)110for level in diff.asks:111action = "REMOVE" if level.sz == "0" else "SET"112print(f" ASK {action:<6} {level.px:>12} | {level.sz:>12}")113114# Changed bid levels115for level in diff.bids:116action = "REMOVE" if level.sz == "0" else "SET"117print(f" BID {action:<6} {level.px:>12} | {level.sz:>12}")118119print(f"\n Messages received: {msg_count}")120121除了 grpc。RpcError 作为 e:122如果 e.代码() == grpc.状态码.DATA_LOSS 和 auto_reconnect:123print(f"\n⚠️ Server reinitialized: {e.details()}")124retry_count += 1125if retry_count < max_retries:126延迟 = base_delay * (2 ** (重试次数 - 1)) # 指数退避127打印(f"⏳ 正在等待 {延迟}秒后重新连接...")128时间.sleep(延迟)129频道.关闭()130继续131else:132打印(f"\n❌ 最大重试次数 ({max_retries}) 已达到。放弃。")133换行134else:135print(f"\ngRPC error: {e.code()} - {e.details()}")136换行137除了 KeyboardInterrupt:138print("\nStopping L2 diff stream...")139换行140最后:141频道.关闭()142143# 如果在此处未出现错误,则退出重试循环144换行145146147def main():148parser = argparse.ArgumentParser(description='Stream Hyperliquid incremental L2 order book diffs via gRPC')149parser.add_argument('--coins', default='BTC', help='Comma-separated coin symbols to stream (e.g., BTC,ETH)')150parser.add_argument('--levels', type=int, default=20, help='Maximum price levels per side (default: 20, max: 100)')151解析器.add_argument('--sig-figs', type=int, 默认值=None, 帮助='分桶法中的有效数字(2-5)')152解析器.add_argument('--mantissa', type=int, 默认值=None, 帮助='分桶小数部分(1、2 或 5)')153parser.add_argument('--skip-snapshot', action='store_true', help='Skip the initial per-coin snapshot')154155args = 解析器.parse_args()156coins = [c.strip() for c in args.coins.split(',') if c.strip()]157158打印(f"\n{'='*60}")159print("Hyperliquid StreamL2BookDiff Example")160print(f"Endpoint: {GRPC_ENDPOINT}")161打印(f"{'='*60}")162163试一试:164stream_l2_book_diff(coins, n_levels=args.levels, n_sig_figs=args.sig_figs, mantissa=args.mantissa, skip_initial_snapshot=args.skip_snapshot)165除 异常 作为 e:166print(f"\nError: {e}")167import traceback168回溯.print_exc()169sys.exit(1)170171172如果 __name__ == "__main__":173主()174