StreamL2BookDiff gRPC Method
이 방식은 데이터 사용량을 기준으로 0.0165 MB당 10 API 크레딧으로 과금된다는 점에 유의하시기 바랍니다.
매개변수
동전
반복되는 문자열
로딩 중...
n_levels
uint32
로딩 중...
n_sig_figs
uint32
로딩 중...
mantissa
uint64
로딩 중...
skip_initial_snapshot
bool
로딩 중...
반품
스트림
stream<L2BookDiffUpdate>
로딩 중...
시간
uint64
로딩 중...
높이
uint64
로딩 중...
스냅샷
bool
로딩 중...
diffs
array<L2CoinDiff>
로딩 중...
요청
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2패키지 main34import (5"문맥"6"깃발"7"fmt"8"io"9"로그"10"수학"11"문자열"12"시간"1314"google.golang.org/grpc"15"google.golang.org/grpc/codes"16"google.golang.org/grpc/credentials"17"google.golang.org/grpc/metadata"18"google.golang.org/grpc/status"1920pb "hyperliquid-orderbook-example/proto"21)2223const (24grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"25authToken = "your-auth-token"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("Levels: %d\n", nLevels)34fmt.PrintlnPrintln ("자동 재연결: 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 {48return fmt.Errorf("연결 실패: %w", err)49}5051클라이언트 := pb.NewOrderBookStreamingClient)(conn)52ctx := 메타데이터.AppendToOutgoingContext(컨텍스트.배경(), "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", printf("%s에 연결 중...\n",)63}6465stream, err := client.StreamL2BookDiff(ctx, request)66만약 err != nil {67conn.닫기()68return fmt.Errorf("스트림 시작에 실패했습니다: %w", err)69}7071msgCount := 072shouldRetry := false7374~을 위해 {75업데이트, 오류 := 스트림.Recv()76만약 err == io.EOF {77중단78}79만약 err != nil {80st, 알겠습니다 := 상태.FromError(err)81만약 ok && st.코드() == 코드.DataLoss {82fmt.Printf("\n⚠️ 서버 재초기화됨: %s\n", st.메시지())83retryCount++84if retryCount < maxRetries {85지연 := 기본지연 * time.Duration(수학.Pow(2, float64(retryCount-1)))86fmt.Printf("⏳ 재연결하기 전 %v 동안 대기 중...\n", delay)87시간.수면(지연)88shouldRetry = true89중단90} else {91fmt.Printf("\n❌ 최대 재시도 횟수(%d)에 도달했습니다. 중단합니다.\n", maxRetries)92conn.닫기()93return nil94}95}96conn.닫기()97return fmt.Errorf("스트림 오류: %w", err)98}99100msgCount++101if msgCount == 1 {102fmt.Println("✓ First L2 diff update received!\n")103retryCount = 0 // 성공 시 재설정104}105106// Display diff update107fmt.PrintlnPrintln ("\n" + strings.반복("─", 60))108snapshotLabel := ""109만약 업데이트.스냅샷 {110스냅샷 레이블 = " | 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}156157return 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")163164깃발.구문 분석()165166동전 := 문자열.Split(*coinsFlag, ",")167168fmt.PrintlnPrintln ("\n" + strings.반복("=", 60))169fmt.Println("Hyperliquid StreamL2BookDiff Example")170fmt.Printf("엔드포인트: %s\n", grpcEndpoint)171fmt.PrintlnPrintln문자열.반복("=", 60))172173if err := streamL2BookDiff(coins, uint32(*levels)); err != nil {174로그.치명적(err)175}176}177
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2패키지 main34import (5"문맥"6"깃발"7"fmt"8"io"9"로그"10"수학"11"문자열"12"시간"1314"google.golang.org/grpc"15"google.golang.org/grpc/codes"16"google.golang.org/grpc/credentials"17"google.golang.org/grpc/metadata"18"google.golang.org/grpc/status"1920pb "hyperliquid-orderbook-example/proto"21)2223const (24grpcEndpoint = "your-endpoint.hype-mainnet.quiknode.pro:10000"25authToken = "your-auth-token"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("Levels: %d\n", nLevels)34fmt.PrintlnPrintln ("자동 재연결: 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 {48return fmt.Errorf("연결 실패: %w", err)49}5051클라이언트 := pb.NewOrderBookStreamingClient)(conn)52ctx := 메타데이터.AppendToOutgoingContext(컨텍스트.배경(), "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", printf("%s에 연결 중...\n",)63}6465stream, err := client.StreamL2BookDiff(ctx, request)66만약 err != nil {67conn.닫기()68return fmt.Errorf("스트림 시작에 실패했습니다: %w", err)69}7071msgCount := 072shouldRetry := false7374~을 위해 {75업데이트, 오류 := 스트림.Recv()76만약 err == io.EOF {77중단78}79만약 err != nil {80st, 알겠습니다 := 상태.FromError(err)81만약 ok && st.코드() == 코드.DataLoss {82fmt.Printf("\n⚠️ 서버 재초기화됨: %s\n", st.메시지())83retryCount++84if retryCount < maxRetries {85지연 := 기본지연 * time.Duration(수학.Pow(2, float64(retryCount-1)))86fmt.Printf("⏳ 재연결하기 전 %v 동안 대기 중...\n", delay)87시간.수면(지연)88shouldRetry = true89중단90} else {91fmt.Printf("\n❌ 최대 재시도 횟수(%d)에 도달했습니다. 중단합니다.\n", maxRetries)92conn.닫기()93return nil94}95}96conn.닫기()97return fmt.Errorf("스트림 오류: %w", err)98}99100msgCount++101if msgCount == 1 {102fmt.Println("✓ First L2 diff update received!\n")103retryCount = 0 // 성공 시 재설정104}105106// Display diff update107fmt.PrintlnPrintln ("\n" + strings.반복("─", 60))108snapshotLabel := ""109만약 업데이트.스냅샷 {110스냅샷 레이블 = " | 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}156157return 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")163164깃발.구문 분석()165166동전 := 문자열.Split(*coinsFlag, ",")167168fmt.PrintlnPrintln ("\n" + strings.반복("=", 60))169fmt.Println("Hyperliquid StreamL2BookDiff Example")170fmt.Printf("엔드포인트: %s\n", grpcEndpoint)171fmt.PrintlnPrintln문자열.반복("=", 60))172173if err := streamL2BookDiff(coins, uint32(*levels)); err != nil {174로그.치명적(err)175}176}177
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2const grpc = require('@grpc/grpc-js');3const protoLoader = require('@grpc/proto-loader');4const path = require('path');56const GRPC_ENDPOINT = 'your-endpoint.hype-mainnet.quiknode.pro:10000';7const AUTH_TOKEN = 'your-auth-token';8const PROTO_PATH = path.join(__dirname, 'proto', 'orderbook.proto');910const 패키지 정의 = protoLoader.loadSync(PROTO_PATH, {11keepCase: true,12longs: String,13열거형: String,14기본값: true,15oneofs: true16});17const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;1819함수 createClient() {20돌아가기 new proto.OrderBookStreaming((21GRPC_ENDPOINT,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콘솔.log('='.반복(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콘솔.log('='.반복(60) + '\n');3435const maxRetries = 10;36const baseDelay = 2000;3738// Track last seq per coin for gap detection39const lastSeq = {};4041while (retryCount < maxRetries) {42const client = createClient();43const 메타데이터 = new grpc.메타데이터();44메타데이터.추가('x-token', AUTH_TOKEN);4546const request = {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);6061호출.on('data', (업데이트) => {62msgCount++;6364만약 (msgCount === 1) {65console.log('✓ First L2 diff update received!\n');66retryCount = 0; // 성공 시 재설정67}6869콘솔.log('\n' + '─'.반복(60));70console.log(`Block: ${update.height} | Time: ${update.time}${update.snapshot ? ' | SNAPSHOT' : ''}`);71콘솔.log('─'.반복(60));7273(업데이트.차이점 || []).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});9899호출.on('error', (err) => {100만약 (err.코드 === grpc.상태.DATA_LOSS && autoReconnect) {101console.log(`\n⚠️ Server reinitialized: ${err.message}`);102retryCount++;103if (retryCount < maxRetries) {104const delay = 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.메시지);112}113});114115호출.on('end', () => {116콘솔.log('\n스트림 종료됨');117});118119// Wait for stream to complete120기다리다 새로운 약속((결심) => {121호출.on('end', 해결);122호출.on('error', 해결);123});124125break; // 성공 시 재시도 루프 종료126127} catch (err) {128콘솔.오류('오류:', err.message);129break;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 levels = parseInt(args.find(a => a.startsWith('--levels='))?.split('=')[1]) || 20;138139콘솔.log('\n' + '='.반복(60));140console.log('Hyperliquid StreamL2BookDiff Example');141console.log(`Endpoint: ${GRPC_ENDPOINT}`);142콘솔.log('='.반복(60));143144streamL2BookDiff(coins, levels);145
1// StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC2const grpc = require('@grpc/grpc-js');3const protoLoader = require('@grpc/proto-loader');4const path = require('path');56const GRPC_ENDPOINT = 'your-endpoint.hype-mainnet.quiknode.pro:10000';7const AUTH_TOKEN = 'your-auth-token';8const PROTO_PATH = path.join(__dirname, 'proto', 'orderbook.proto');910const 패키지 정의 = protoLoader.loadSync(PROTO_PATH, {11keepCase: true,12longs: String,13열거형: String,14기본값: true,15oneofs: true16});17const proto = grpc.loadPackageDefinition(packageDefinition).hyperliquid;1819함수 createClient() {20돌아가기 new proto.OrderBookStreaming((21GRPC_ENDPOINT,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콘솔.log('='.반복(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콘솔.log('='.반복(60) + '\n');3435const maxRetries = 10;36const baseDelay = 2000;3738// Track last seq per coin for gap detection39const lastSeq = {};4041while (retryCount < maxRetries) {42const client = createClient();43const 메타데이터 = new grpc.메타데이터();44메타데이터.추가('x-token', AUTH_TOKEN);4546const request = {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);6061호출.on('data', (업데이트) => {62msgCount++;6364만약 (msgCount === 1) {65console.log('✓ First L2 diff update received!\n');66retryCount = 0; // 성공 시 재설정67}6869콘솔.log('\n' + '─'.반복(60));70console.log(`Block: ${update.height} | Time: ${update.time}${update.snapshot ? ' | SNAPSHOT' : ''}`);71콘솔.log('─'.반복(60));7273(업데이트.차이점 || []).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});9899호출.on('error', (err) => {100만약 (err.코드 === grpc.상태.DATA_LOSS && autoReconnect) {101console.log(`\n⚠️ Server reinitialized: ${err.message}`);102retryCount++;103if (retryCount < maxRetries) {104const delay = 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.메시지);112}113});114115호출.on('end', () => {116콘솔.log('\n스트림 종료됨');117});118119// Wait for stream to complete120기다리다 새로운 약속((결심) => {121호출.on('end', 해결);122호출.on('error', 해결);123});124125break; // 성공 시 재시도 루프 종료126127} catch (err) {128콘솔.오류('오류:', err.message);129break;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 levels = parseInt(args.find(a => a.startsWith('--levels='))?.split('=')[1]) || 20;138139콘솔.log('\n' + '='.반복(60));140console.log('Hyperliquid StreamL2BookDiff Example');141console.log(`Endpoint: ${GRPC_ENDPOINT}`);142콘솔.log('='.반복(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 -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto89사용법:10python stream_l2_diff_example.py --coins BTC,ETH --levels 2011"""1213import grpc14import sys15import 시간16import argparse17from typing import Optional1819시도해 보세요:20import orderbook_pb2 다음과 같이 pb21import orderbook_pb2_grpc 다음과 같이 pb_grpc22except ImportError:23인쇄("오류: Proto 파일이 생성되지 않았습니다. 다음을 실행하십시오:")24인쇄(" python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto")25sys.exit(1)2627# 구성28GRPC_ENDPOINT = "your-endpoint.hype-mainnet.quiknode.pro:10000"29AUTH_TOKEN = "your-auth-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: Significance figures for price bucketing (2-5)40mantissa: Mantissa for bucketing (1, 2, or 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_ENDPOINT,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.OrderBookStreamingStub(채널)6768# 빌드 요청69request = pb.L2BookDiffRequest(70coins=coins,71n_levels=n_levels,72skip_initial_snapshot=skip_initial_snapshot73)74if n_sig_figs is not None:75request.n_sig_figs = n_sig_figs76if mantissa is not None:77request.mantissa = mantissa7879msg_count = 08081시도해 보세요:82if retry_count > 0:83인쇄(f"\n🔄 다시 연결 중 (시도 중 {재시도 횟수 + 1}/{최대 재시도 횟수})...")84else:85인쇄(f"연결 중 {GRPC_ENDPOINT}...")8687for update in stub.StreamL2BookDiff(request, metadata=[('x-token', AUTH_TOKEN)]):88msg_count += 18990if 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.스냅샷 else ""97print(f"Block: {update.height} | Time: {update.time}{snapshot_label}")98인쇄(f"{'─'*60}")99100~을 위해 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.StatusCode.DATA_LOSS 및 auto_reconnect:123print(f"\n⚠️ Server reinitialized: {e.details()}")124retry_count += 1125if retry_count < max_retries:126지연 = 기본_지연 * (2 ** (retry_count - 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)')151parser.add_argument('--sig-figs', type=int, default=None, help='Significance figures for bucketing (2-5)')152parser.add_argument('--mantissa', type=int, default=None, help='Mantissa for bucketing (1, 2, or 5)')153parser.add_argument('--skip-snapshot', action='store_true', help='Skip the initial per-coin snapshot')154155args = parser.parse_args()156동전 = [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 트레이스백168트레이스백.print_exc()169sys.exit(1)170171172if __name__ == "__main__":173main()174
1#!/usr/bin/env python32"""3StreamL2BookDiff Example - Stream incremental L2 price-level changes via gRPC45설정:6pip install grpcio grpcio-tools protobuf zstandard7python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto89사용법:10python stream_l2_diff_example.py --coins BTC,ETH --levels 2011"""1213import grpc14import sys15import 시간16import argparse17from typing import Optional1819시도해 보세요:20import orderbook_pb2 다음과 같이 pb21import orderbook_pb2_grpc 다음과 같이 pb_grpc22except ImportError:23인쇄("오류: Proto 파일이 생성되지 않았습니다. 다음을 실행하십시오:")24인쇄(" python -m grpc_tools.protoc -I../../proto --python_out=. --grpc_python_out=. ../../proto/orderbook.proto")25sys.exit(1)2627# 구성28GRPC_ENDPOINT = "your-endpoint.hype-mainnet.quiknode.pro:10000"29AUTH_TOKEN = "your-auth-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: Significance figures for price bucketing (2-5)40mantissa: Mantissa for bucketing (1, 2, or 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_ENDPOINT,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.OrderBookStreamingStub(채널)6768# 빌드 요청69request = pb.L2BookDiffRequest(70coins=coins,71n_levels=n_levels,72skip_initial_snapshot=skip_initial_snapshot73)74if n_sig_figs is not None:75request.n_sig_figs = n_sig_figs76if mantissa is not None:77request.mantissa = mantissa7879msg_count = 08081시도해 보세요:82if retry_count > 0:83인쇄(f"\n🔄 다시 연결 중 (시도 중 {재시도 횟수 + 1}/{최대 재시도 횟수})...")84else:85인쇄(f"연결 중 {GRPC_ENDPOINT}...")8687for update in stub.StreamL2BookDiff(request, metadata=[('x-token', AUTH_TOKEN)]):88msg_count += 18990if 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.스냅샷 else ""97print(f"Block: {update.height} | Time: {update.time}{snapshot_label}")98인쇄(f"{'─'*60}")99100~을 위해 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.StatusCode.DATA_LOSS 및 auto_reconnect:123print(f"\n⚠️ Server reinitialized: {e.details()}")124retry_count += 1125if retry_count < max_retries:126지연 = 기본_지연 * (2 ** (retry_count - 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)')151parser.add_argument('--sig-figs', type=int, default=None, help='Significance figures for bucketing (2-5)')152parser.add_argument('--mantissa', type=int, default=None, help='Mantissa for bucketing (1, 2, or 5)')153parser.add_argument('--skip-snapshot', action='store_true', help='Skip the initial per-coin snapshot')154155args = parser.parse_args()156동전 = [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 트레이스백168트레이스백.print_exc()169sys.exit(1)170171172if __name__ == "__main__":173main()174