GetOrderBookSnapshot
Retrieve a single order book snapshot at a specific point in time from the Hyperliquid L1 Gateway.
When to Use This Method
GetOrderBookSnapshot
is essential for:
- Market Analysis - Get current market depth and liquidity
- Trading Strategies - Analyze bid/ask spreads and order distribution
- Price Discovery - Determine fair market value
- Risk Management - Assess market impact before placing orders
Method Signature
rpc GetOrderBookSnapshot(Timestamp) returns (OrderBookSnapshot) {}
Request Message
message Timestamp {
int64 timestamp = 1;
}
Current Limitation: The timestamp parameter is not yet implemented. This method currently returns the most recent order book snapshot regardless of the timestamp value provided.
Response Message
message OrderBookSnapshot {
// JSON-encoded object conforming to a
// Hyperliquid order book snapshot
bytes data = 1;
}
The data
field contains a JSON-encoded order book snapshot with the following structure:
Data Structure
{
"coin": "string", // Trading pair symbol (e.g., "@1" for ETH)
"time": 1757672867000, // Unix timestamp in milliseconds
"levels": [
[ // Bid levels (index 0)
{
"px": "18.414", // Price level
"sz": "100.0", // Total size at this level
"n": 1 // Number of orders at this level
},
// ... more bid levels
],
[ // Ask levels (index 1)
{
"px": "18.515", // Price level
"sz": "50.5", // Total size at this level
"n": 2 // Number of orders at this level
},
// ... more ask levels
]
]
}
Field Descriptions
Field | Type | Description |
---|---|---|
coin | string | The trading pair symbol |
time | number | Unix timestamp in milliseconds |
levels[0] | array | Bid orders sorted by price (descending) |
levels[1] | array | Ask orders sorted by price (ascending) |
px | string | Price at this level |
sz | string | Total size/volume at this price level |
n | number | Number of individual orders at this level |
Implementation Examples
- Go
- Python
- Node.js
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
pb "your-project/hyperliquid_l1_gateway/v1"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
type OrderBookLevel struct {
Price string `json:"px"`
Size string `json:"sz"`
Orders int `json:"n"`
}
type OrderBookData struct {
Coin string `json:"coin"`
Time float64 `json:"time"`
Levels [][]OrderBookLevel `json:"levels"`
}
func getOrderBookSnapshot(client pb.HyperLiquidL1GatewayClient) (*OrderBookData, error) {
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Request current snapshot (timestamp 0 = latest)
request := &pb.Timestamp{Timestamp: 0}
response, err := client.GetOrderBookSnapshot(ctx, request)
if err != nil {
return nil, fmt.Errorf("RPC call failed: %v", err)
}
// Parse JSON response
var orderBook OrderBookData
if err := json.Unmarshal(response.Data, &orderBook); err != nil {
return nil, fmt.Errorf("failed to parse JSON: %v", err)
}
return &orderBook, nil
}
func main() {
const endpoint = "subsquid-hyperliquid-mainnet.n.dwellir.com:443"
// Set up TLS connection
creds := credentials.NewTLS(nil)
// Connection options
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(150 * 1024 * 1024), // 150MB max
),
}
// Connect to server
conn, err := grpc.NewClient(endpoint, opts...)
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Create the client
client := pb.NewHyperLiquidL1GatewayClient(conn)
// Get order book snapshot
orderBook, err := getOrderBookSnapshot(client)
if err != nil {
log.Fatalf("Failed to get order book: %v", err)
}
// Process the response
processOrderBook(orderBook)
}
func processOrderBook(orderBook *OrderBookData) {
// Display basic information
fmt.Printf("Coin: %s\n", orderBook.Coin)
// Convert timestamp
timestamp := time.Unix(int64(orderBook.Time/1000), 0)
fmt.Printf("Time: %s\n", timestamp.Format("2006-01-02 15:04:05 UTC"))
if len(orderBook.Levels) >= 2 {
bids := orderBook.Levels[0]
asks := orderBook.Levels[1]
fmt.Printf("\nBids: %d levels\n", len(bids))
fmt.Printf("Asks: %d levels\n", len(asks))
// Display best bid and ask
if len(bids) > 0 && len(asks) > 0 {
fmt.Printf("\nBest Bid: %s @ %s (Orders: %d)\n",
bids[0].Size, bids[0].Price, bids[0].Orders)
fmt.Printf("Best Ask: %s @ %s (Orders: %d)\n",
asks[0].Size, asks[0].Price, asks[0].Orders)
// Calculate spread
spread := calculateSpread(bids[0].Price, asks[0].Price)
fmt.Printf("Spread: %.6f\n", spread)
}
// Display market depth (top 5 levels)
fmt.Println("\nMarket Depth (Top 5):")
displayDepth(bids, asks, 5)
}
}
func calculateSpread(bidPrice, askPrice string) float64 {
var bid, ask float64
fmt.Sscanf(bidPrice, "%f", &bid)
fmt.Sscanf(askPrice, "%f", &ask)
return ask - bid
}
func displayDepth(bids, asks []OrderBookLevel, levels int) {
maxLevels := min(levels, len(bids), len(asks))
fmt.Println(" Bids | Asks")
fmt.Println(" --------------- | ---------------")
for i := 0; i < maxLevels; i++ {
fmt.Printf(" %s @ %-8s | %s @ %s\n",
bids[i].Size, bids[i].Price,
asks[i].Size, asks[i].Price)
}
}
func min(values ...int) int {
minVal := values[0]
for _, v := range values[1:] {
if v < minVal {
minVal = v
}
}
return minVal
}
import grpc
import json
import time
from datetime import datetime
from hyperliquid_l1_gateway_pb2 import Timestamp
from hyperliquid_l1_gateway_pb2_grpc import HyperliquidL1GatewayStub
class OrderBookAnalyzer:
def __init__(self, endpoint='subsquid-hyperliquid-mainnet.n.dwellir.com:443'):
# Use secure channel with TLS
channel_options = [
('grpc.max_receive_message_length', 150 * 1024 * 1024), # 150MB
]
credentials = grpc.ssl_channel_credentials()
self.channel = grpc.secure_channel(endpoint, credentials, options=channel_options)
self.stub = HyperliquidL1GatewayStub(self.channel)
def get_snapshot(self, timeout=30):
"""Get current order book snapshot"""
timestamp = Timestamp(timestamp=0) # 0 = latest snapshot
response = self.stub.GetOrderBookSnapshot(timestamp, timeout=timeout)
# Parse JSON data
data = json.loads(response.data)
return data
def analyze_spread(self, order_book):
"""Calculate bid-ask spread"""
levels = order_book.get('levels', [])
if len(levels) < 2:
return None
bids = levels[0] # Bid levels
asks = levels[1] # Ask levels
if not bids or not asks:
return None
best_bid = float(bids[0]['px'])
best_ask = float(asks[0]['px'])
spread = best_ask - best_bid
spread_bps = (spread / best_bid) * 10000
return {
'best_bid': best_bid,
'best_ask': best_ask,
'best_bid_size': float(bids[0]['sz']),
'best_ask_size': float(asks[0]['sz']),
'spread': spread,
'spread_bps': spread_bps
}
def calculate_depth(self, order_book, levels=10):
"""Calculate market depth"""
book_levels = order_book.get('levels', [])
if len(book_levels) < 2:
return None
bids = book_levels[0][:levels]
asks = book_levels[1][:levels]
bid_volume = sum(float(level['sz']) for level in bids)
ask_volume = sum(float(level['sz']) for level in asks)
# Count total orders
bid_orders = sum(level['n'] for level in bids)
ask_orders = sum(level['n'] for level in asks)
return {
'bid_depth': bid_volume,
'ask_depth': ask_volume,
'total_depth': bid_volume + ask_volume,
'bid_orders': bid_orders,
'ask_orders': ask_orders,
'depth_imbalance': (bid_volume - ask_volume) / (bid_volume + ask_volume) if (bid_volume + ask_volume) > 0 else 0
}
def display_order_book(self, order_book, max_levels=5):
"""Display order book in a formatted way"""
coin = order_book.get('coin', 'Unknown')
timestamp = order_book.get('time', 0)
# Convert timestamp to readable format
dt = datetime.fromtimestamp(timestamp / 1000)
print(f"\n{'='*50}")
print(f"Order Book Snapshot for {coin}")
print(f"Time: {dt.strftime('%Y-%m-%d %H:%M:%S UTC')}")
print(f"{'='*50}")
levels = order_book.get('levels', [])
if len(levels) >= 2:
bids = levels[0][:max_levels]
asks = levels[1][:max_levels]
print(f"\n{'Bids':<25} | {'Asks':>25}")
print(f"{'-'*25} | {'-'*25}")
for i in range(max(len(bids), len(asks))):
bid_str = ""
ask_str = ""
if i < len(bids):
bid = bids[i]
bid_str = f"{float(bid['sz']):>10.4f} @ {float(bid['px']):>10.6f}"
if i < len(asks):
ask = asks[i]
ask_str = f"{float(ask['sz']):>10.4f} @ {float(ask['px']):>10.6f}"
print(f"{bid_str:<25} | {ask_str:>25}")
def main():
# Initialize analyzer with endpoint
analyzer = OrderBookAnalyzer('subsquid-hyperliquid-mainnet.n.dwellir.com:443')
try:
# Get order book snapshot
print("Fetching order book snapshot...")
order_book = analyzer.get_snapshot()
# Display order book
analyzer.display_order_book(order_book)
# Analyze spread
spread_info = analyzer.analyze_spread(order_book)
if spread_info:
print(f"\nMarket Analysis:")
print(f"Best bid: {spread_info['best_bid']:.6f} (Size: {spread_info['best_bid_size']:.4f})")
print(f"Best ask: {spread_info['best_ask']:.6f} (Size: {spread_info['best_ask_size']:.4f})")
print(f"Spread: {spread_info['spread']:.6f} ({spread_info['spread_bps']:.2f} bps)")
# Calculate depth
depth_info = analyzer.calculate_depth(order_book)
if depth_info:
print(f"\nMarket Depth (Top 10 levels):")
print(f"Bid depth: {depth_info['bid_depth']:.4f} ({depth_info['bid_orders']} orders)")
print(f"Ask depth: {depth_info['ask_depth']:.4f} ({depth_info['ask_orders']} orders)")
print(f"Total depth: {depth_info['total_depth']:.4f}")
print(f"Depth imbalance: {depth_info['depth_imbalance']:.3f}")
except grpc.RpcError as e:
print(f"gRPC error: {e.code()} - {e.details()}")
except json.JSONDecodeError as e:
print(f"JSON parsing error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
finally:
analyzer.channel.close()
if __name__ == '__main__':
main()
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
// Load proto file
const packageDefinition = protoLoader.loadSync(
'hyperliquid_l1_gateway.proto',
{
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
}
);
class OrderBookClient {
constructor(endpoint = 'subsquid-hyperliquid-mainnet.n.dwellir.com:443') {
const proto = grpc.loadPackageDefinition(packageDefinition);
// Use SSL credentials for secure connection
const credentials = grpc.credentials.createSsl();
// Set max message size
const options = {
'grpc.max_receive_message_length': 150 * 1024 * 1024, // 150MB
};
this.client = new proto.hyperliquid_l1_gateway.v1.HyperliquidL1Gateway(
endpoint,
credentials,
options
);
}
async getSnapshot() {
return new Promise((resolve, reject) => {
// Set timeout
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 30);
this.client.GetOrderBookSnapshot(
{ timestamp: 0 }, // 0 = latest snapshot
{ deadline },
(error, response) => {
if (error) {
reject(error);
return;
}
try {
const data = JSON.parse(response.data.toString());
resolve(data);
} catch (parseError) {
reject(parseError);
}
}
);
});
}
calculateMetrics(orderBook) {
const levels = orderBook.levels || [];
if (levels.length < 2) {
return null;
}
const bids = levels[0]; // Bid levels
const asks = levels[1]; // Ask levels
if (!bids.length || !asks.length) {
return null;
}
const bestBid = parseFloat(bids[0].px);
const bestAsk = parseFloat(asks[0].px);
const spread = bestAsk - bestBid;
// Calculate total volume at top levels
const topLevels = 10;
const bidVolume = bids.slice(0, topLevels).reduce(
(sum, level) => sum + parseFloat(level.sz), 0
);
const askVolume = asks.slice(0, topLevels).reduce(
(sum, level) => sum + parseFloat(level.sz), 0
);
// Count orders
const bidOrders = bids.slice(0, topLevels).reduce(
(sum, level) => sum + level.n, 0
);
const askOrders = asks.slice(0, topLevels).reduce(
(sum, level) => sum + level.n, 0
);
return {
bestBid,
bestAsk,
bestBidSize: parseFloat(bids[0].sz),
bestAskSize: parseFloat(asks[0].sz),
spread,
spreadBps: (spread / bestBid) * 10000,
bidVolume,
askVolume,
totalVolume: bidVolume + askVolume,
bidOrders,
askOrders,
depthImbalance: (bidVolume - askVolume) / (bidVolume + askVolume)
};
}
displayOrderBook(orderBook, maxLevels = 5) {
const coin = orderBook.coin || 'Unknown';
const timestamp = orderBook.time || 0;
// Convert timestamp to readable format
const date = new Date(timestamp);
console.log('\n' + '='.repeat(50));
console.log(`Order Book Snapshot for ${coin}`);
console.log(`Time: ${date.toISOString()}`);
console.log('='.repeat(50));
const levels = orderBook.levels || [];
if (levels.length >= 2) {
const bids = levels[0].slice(0, maxLevels);
const asks = levels[1].slice(0, maxLevels);
console.log('\n' + 'Bids'.padEnd(25) + ' | ' + 'Asks'.padStart(25));
console.log('-'.repeat(25) + ' | ' + '-'.repeat(25));
const maxRows = Math.max(bids.length, asks.length);
for (let i = 0; i < maxRows; i++) {
let bidStr = '';
let askStr = '';
if (i < bids.length) {
const bid = bids[i];
const size = parseFloat(bid.sz).toFixed(4);
const price = parseFloat(bid.px).toFixed(6);
bidStr = `${size.padStart(10)} @ ${price.padStart(10)}`;
}
if (i < asks.length) {
const ask = asks[i];
const size = parseFloat(ask.sz).toFixed(4);
const price = parseFloat(ask.px).toFixed(6);
askStr = `${size.padStart(10)} @ ${price.padStart(10)}`;
}
console.log(bidStr.padEnd(25) + ' | ' + askStr.padStart(25));
}
console.log('\nLevel Summary:');
console.log(`Total Bid Levels: ${levels[0].length}`);
console.log(`Total Ask Levels: ${levels[1].length}`);
}
}
}
async function main() {
const client = new OrderBookClient('subsquid-hyperliquid-mainnet.n.dwellir.com:443');
try {
console.log('Fetching order book snapshot...');
const orderBook = await client.getSnapshot();
// Display formatted order book
client.displayOrderBook(orderBook);
// Calculate and display metrics
const metrics = client.calculateMetrics(orderBook);
if (metrics) {
console.log('\nMarket Analysis:');
console.log(`Best bid: ${metrics.bestBid.toFixed(6)} (Size: ${metrics.bestBidSize.toFixed(4)})`);
console.log(`Best ask: ${metrics.bestAsk.toFixed(6)} (Size: ${metrics.bestAskSize.toFixed(4)})`);
console.log(`Spread: ${metrics.spread.toFixed(6)} (${metrics.spreadBps.toFixed(2)} bps)`);
console.log('\nMarket Depth (Top 10 levels):');
console.log(`Bid depth: ${metrics.bidVolume.toFixed(4)} (${metrics.bidOrders} orders)`);
console.log(`Ask depth: ${metrics.askVolume.toFixed(4)} (${metrics.askOrders} orders)`);
console.log(`Total depth: ${metrics.totalVolume.toFixed(4)}`);
console.log(`Depth imbalance: ${metrics.depthImbalance.toFixed(3)}`);
}
} catch (error) {
console.error('Error:', error.message);
if (error.code) {
console.error('gRPC code:', error.code);
}
}
}
main();
Common Use Cases
1. Market Making Strategy
async function marketMakingSignals(client) {
const orderBook = await client.getSnapshot();
const metrics = client.calculateMetrics(orderBook);
if (!metrics) return null;
// Calculate fair value and optimal spread
const midPrice = (metrics.bestBid + metrics.bestAsk) / 2;
const optimalSpread = Math.max(metrics.spread * 0.8, 0.001); // Minimum spread
// Analyze order book imbalance for pricing adjustment
const imbalanceAdjustment = metrics.depthImbalance * 0.0001; // Adjust based on imbalance
return {
fairValue: midPrice,
bidPrice: midPrice - (optimalSpread / 2) + imbalanceAdjustment,
askPrice: midPrice + (optimalSpread / 2) + imbalanceAdjustment,
marketDepth: metrics.totalVolume,
spreadTightness: metrics.spreadBps,
imbalance: metrics.depthImbalance,
bidOrders: metrics.bidOrders,
askOrders: metrics.askOrders
};
}
2. Liquidity Assessment
def assess_liquidity(order_book, target_size):
"""Assess market impact for a given trade size"""
levels = order_book.get('levels', [])
if len(levels) < 2:
return None
bids = levels[0] # Bid levels
asks = levels[1] # Ask levels
def calculate_slippage(levels, size, is_buy):
filled = 0
weighted_price = 0
orders_hit = 0
for level in levels:
price = float(level['px'])
volume = float(level['sz'])
num_orders = level['n']
fill_amount = min(size - filled, volume)
weighted_price += price * fill_amount
filled += fill_amount
orders_hit += num_orders
if filled >= size:
break
return {
'avg_price': weighted_price / filled if filled > 0 else None,
'filled': filled,
'orders_hit': orders_hit
}
# Calculate slippage for buy and sell
buy_impact = calculate_slippage(asks, target_size, True) # Buy from asks
sell_impact = calculate_slippage(bids, target_size, False) # Sell to bids
if not bids or not asks:
return None
best_bid = float(bids[0]['px'])
best_ask = float(asks[0]['px'])
mid_price = (best_bid + best_ask) / 2
result = {
'mid_price': mid_price,
'target_size': target_size,
'can_buy': buy_impact['filled'] >= target_size,
'can_sell': sell_impact['filled'] >= target_size
}
if buy_impact['avg_price']:
result['buy_price'] = buy_impact['avg_price']
result['buy_slippage'] = (buy_impact['avg_price'] - mid_price) / mid_price
result['buy_orders_hit'] = buy_impact['orders_hit']
if sell_impact['avg_price']:
result['sell_price'] = sell_impact['avg_price']
result['sell_slippage'] = (mid_price - sell_impact['avg_price']) / mid_price
result['sell_orders_hit'] = sell_impact['orders_hit']
return result
3. Real-time Price Display
func displayOrderBookLadder(orderBook *OrderBookData) {
fmt.Println("\n=== ORDER BOOK LADDER ===")
fmt.Printf("Coin: %s\n", orderBook.Coin)
// Convert and display timestamp
timestamp := time.Unix(int64(orderBook.Time/1000), 0)
fmt.Printf("Time: %s\n", timestamp.Format("15:04:05 UTC"))
if len(orderBook.Levels) < 2 {
fmt.Println("No order book data available")
return
}
bids := orderBook.Levels[0]
asks := orderBook.Levels[1]
// Display top 5 asks in reverse order (highest price first)
fmt.Println("\n ASKS")
fmt.Println(" Orders | Size | Price")
fmt.Println(" -------|-------------|------------")
displayCount := min(5, len(asks))
for i := displayCount - 1; i >= 0; i-- {
ask := asks[i]
fmt.Printf(" %6d | %11s | %10s\n",
ask.Orders, ask.Size, ask.Price)
}
// Display spread line
fmt.Println(" ======================================")
if len(bids) > 0 && len(asks) > 0 {
spread := calculateSpread(bids[0].Price, asks[0].Price)
fmt.Printf(" SPREAD: %.6f\n", spread)
}
fmt.Println(" ======================================")
// Display top 5 bids
fmt.Println(" BIDS")
fmt.Println(" Orders | Size | Price")
fmt.Println(" -------|-------------|------------")
for i := 0; i < min(5, len(bids)); i++ {
bid := bids[i]
fmt.Printf(" %6d | %11s | %10s\n",
bid.Orders, bid.Size, bid.Price)
}
// Display summary
fmt.Printf("\nTotal Levels: %d bids, %d asks\n",
len(bids), len(asks))
}
Error Handling
async function robustGetSnapshot(client, retries = 3) {
for (let attempt = 0; attempt < retries; attempt++) {
try {
return await client.getSnapshot();
} catch (error) {
console.warn(`Attempt ${attempt + 1} failed:`, error.message);
if (attempt === retries - 1) {
throw error;
}
// Exponential backoff
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}
Best Practices
- Cache Management: Order book snapshots can be cached briefly (1-5 seconds) for non-critical applications
- Data Validation: Always validate JSON structure and numerical values
- Error Recovery: Implement retry logic with exponential backoff
- Resource Management: Close gRPC connections properly to avoid resource leaks
- Performance: For high-frequency updates, consider using the streaming methods instead
Current Limitations
- Historical Data: Timestamp parameter is not implemented; only current snapshots available
- Data Retention: Historical order book data is limited to a 24-hour rolling window
- Rate Limits: Be mindful of request frequency to avoid overwhelming the service
Need help? Contact our support team or check the Hyperliquid gRPC documentation.