StreamFills - Real-time Fill Streaming
Stream continuous fill data from Hyperliquid L1 Gateway via gRPC. Monitor order executions in real-time.
Stream continuous fill data starting from a position, providing real-time access to order executions on Hyperliquid.
Full Code Examples
Clone our gRPC Code Examples Repository for complete, runnable implementations. See the copy-trading-bot for a production-ready example using StreamFills.
When to Use This Method
StreamFills is essential for:
- Trade Monitoring - Track order executions in real-time
- Position Management - Monitor fills for active trading strategies
- Settlement Tracking - Verify order completions and partial fills
- Analytics & Reporting - Collect comprehensive trade execution data
Method Signature
rpc StreamFills(Position) returns (stream BlockFills) {}Request Parameters
One of `position`. ms since Unix epoch, inclusive
One of `position`. block height, inclusive
Response Body
Fill execution details (price, size, side) Order identifiers (order ID, client order ID) Counterparty information Timestamp and block reference
Response Stream
message BlockFills {
// JSON-encoded object from "node_fills" or "node_fills_by_block".
bytes data = 1;
}The data field contains a JSON-encoded fills object with:
- Fill execution details (price, size, side)
- Order identifiers (order ID, client order ID)
- Counterparty information
- Timestamp and block reference
Full Fill Spec
StreamFills emits a JSON payload matching Hyperliquid's node_fills format. Below is the exact structure.
Top-level keys (always present):
{
"local_time": "2025-07-27T08:50:10.334741319", // ISO-8601 timestamp when node processed fill (nanosecond precision)
"block_time": "2025-07-27T08:50:10.273720809", // ISO-8601 timestamp from block consensus
"block_number": 676607012, // Block height containing this fill
"events": [ // Array of [address, fill_data] pairs
[
"0x7839e2f2c375dd2935193f2736167514efff9916", // User address (40 hex chars, lowercase)
{
"coin": "BTC", // Trading pair symbol
"px": "118136.0", // Fill price (string)
"sz": "0.00009", // Fill size (string)
"side": "B", // "B" (buy) or "A" (sell/ask)
"time": 1753606210273, // Fill timestamp (ms since Unix epoch)
"startPosition": "-1.41864", // Position size before fill (string)
"dir": "Close Short", // Direction: "Open Long" | "Open Short" | "Close Long" | "Close Short"
"closedPnl": "-0.003753", // Realized PnL from closing position (string, can be negative)
"hash": "0xe7822040155eaa2e737e042854342401120052bbf063906ce8c8f3babe853a79", // Transaction hash (64 hex)
"oid": 121670079265, // Order ID (numeric)
"crossed": false, // Whether order crossed the spread
"fee": "-0.000212", // Trading fee (string, negative = rebate)
"tid": 161270588369408, // Trade ID (unique identifier)
"cloid": "0x09367b9f8541c581f95b02aaf05f1508", // Client order ID (optional, 32 hex)
"feeToken": "USDC", // Token used for fee payment
"builder": "0x49ae63056b3a0be0b166813ee687309ab653c07c", // Builder address (optional)
"builderFee": "0.005528" // Builder fee amount (optional, string)
}
]
// ... more [address, fill_data] pairs
]
}Fill event entry (events[i]):
[
"0x...", // user_address - the trader's address
{
// Fill details object
}
]Fill details fields:
| Field | Type | Description |
|---|---|---|
coin | string | Trading pair symbol (e.g., "BTC", "ETH", "SOL") |
px | string | Fill price |
sz | string | Fill size |
side | string | "B" for buy, "A" for sell |
time | number | Fill timestamp in milliseconds since Unix epoch |
startPosition | string | Position size before this fill |
dir | string | Position direction: "Open Long", "Open Short", "Close Long", "Close Short" |
closedPnl | string | Realized PnL if closing a position (can be negative) |
hash | string | Transaction hash (64 hex characters) |
oid | number | Order ID assigned by the system |
crossed | boolean | Whether the order crossed the spread (taker vs maker) |
fee | string | Trading fee (negative values indicate rebates) |
tid | number | Unique trade identifier |
cloid | string | Client order ID if provided (optional) |
feeToken | string | Token used for fee payment |
builder | string | Builder address if order was routed through a builder (optional) |
builderFee | string | Fee paid to builder (optional) |
Guarantees and alignment:
eventsarray contains all fills from the block for all users.- Each event pairs a user address with their fill details.
block_numbercorresponds toabci_block.roundin StreamBlocks.block_timealigns withabci_block.timein StreamBlocks.- Multiple fills per block are delivered in a single message.
Developer tips:
- Use
tidas the unique identifier for deduplication. - Track
startPositionanddirto reconstruct position changes. - Sum
closedPnlacross fills to calculate realized PnL. - Normalize
px,sz,fee, andclosedPnl(strings) to numeric types as appropriate. - Handle optional fields (
cloid,builder,builderFee) defensively.
Common Use Cases
1. Trade Execution Tracker
class TradeExecutionTracker {
constructor(streamManager) {
this.streamManager = streamManager;
this.executedTrades = new Map();
streamManager.on('fill', (fillData) => {
this.processFill(fillData);
});
}
processFill(fillData) {
// Track each fill by order ID
const orderId = fillData.oid;
if (!this.executedTrades.has(orderId)) {
this.executedTrades.set(orderId, {
orderId,
fills: [],
totalFilled: 0,
avgPrice: 0
});
}
const trade = this.executedTrades.get(orderId);
trade.fills.push(fillData);
trade.totalFilled += parseFloat(fillData.sz);
// Recalculate average price
const totalValue = trade.fills.reduce(
(sum, f) => sum + parseFloat(f.px) * parseFloat(f.sz), 0
);
trade.avgPrice = totalValue / trade.totalFilled;
console.log(`Order ${orderId}: Filled ${trade.totalFilled} @ avg ${trade.avgPrice}`);
}
}2. Fill Rate Monitor
class FillRateMonitor:
def __init__(self):
self.fills_per_minute = []
self.current_minute_fills = 0
self.last_minute = None
def record_fill(self, fill_data):
"""Record a fill and track rates"""
from datetime import datetime
current_minute = datetime.now().replace(second=0, microsecond=0)
if self.last_minute != current_minute:
if self.last_minute is not None:
self.fills_per_minute.append({
'minute': self.last_minute,
'count': self.current_minute_fills
})
self.last_minute = current_minute
self.current_minute_fills = 0
self.current_minute_fills += 1
def get_average_rate(self, minutes=5):
"""Get average fills per minute over last N minutes"""
recent = self.fills_per_minute[-minutes:]
if not recent:
return 0
return sum(r['count'] for r in recent) / len(recent)3. Position Reconciliation
type PositionReconciler struct {
client pb.HyperliquidL1GatewayClient
positions map[string]float64
}
func (pr *PositionReconciler) ReconcileFills(ctx context.Context) {
stream, err := pr.client.StreamFills(ctx, &pb.Position{})
if err != nil {
log.Fatal(err)
}
for {
fill, err := stream.Recv()
if err != nil {
log.Printf("Stream error: %v", err)
return
}
pr.updatePosition(fill.Data)
}
}
func (pr *PositionReconciler) updatePosition(data []byte) {
var fillData map[string]interface{}
json.Unmarshal(data, &fillData)
// Extract fill details and update position
if coin, ok := fillData["coin"].(string); ok {
size, _ := fillData["sz"].(float64)
side, _ := fillData["side"].(string)
if side == "B" {
pr.positions[coin] += size
} else {
pr.positions[coin] -= size
}
log.Printf("Position %s: %f", coin, pr.positions[coin])
}
}Error Handling and Reconnection
class RobustFillStreamer {
constructor(endpoint) {
this.endpoint = endpoint;
this.maxRetries = 5;
this.retryDelay = 1000;
this.currentRetries = 0;
}
async startStreamWithRetry() {
while (this.currentRetries < this.maxRetries) {
try {
await this.startStream();
this.currentRetries = 0;
this.retryDelay = 1000;
} catch (error) {
this.currentRetries++;
console.error(`Stream attempt ${this.currentRetries} failed:`, error.message);
if (this.currentRetries >= this.maxRetries) {
throw new Error('Max retry attempts exceeded');
}
// Exponential backoff
await this.sleep(this.retryDelay);
this.retryDelay *= 2;
}
}
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}Best Practices
- Connection Management: Implement robust reconnection logic with exponential backoff
- Memory Management: Use bounded collections for storing recent fills to prevent memory leaks
- Performance: Process fills asynchronously to avoid blocking the stream
- Monitoring: Track stream health and fill rates
- Error Recovery: Handle various error types (network, parsing, processing) gracefully
- Resource Cleanup: Properly close streams and connections on shutdown
Current Limitations
- Historical Data: Cannot stream from historical timestamps; only real-time streaming available
- Data Retention: Node maintains only 24 hours of historical fill data
- Backpressure: High-volume periods may require careful handling to avoid overwhelming downstream systems
Resources
- GitHub: gRPC Code Examples - Complete working examples
- Copy Trading Bot - Uses
StreamFillsto mirror trades in real-time
Need help? Contact our support team or check the Hyperliquid gRPC documentation.
StreamBlocks - Real-time Block Streaming
Stream continuous block data from Hyperliquid L1 Gateway via gRPC. Complete reference for all 47 action types.
StreamOrderbookSnapshots - Real-time Order Book Streaming
Stream continuous order book snapshots with individual order visibility from Hyperliquid L1 Gateway via gRPC. Premium endpoint available on dedicated nodes.