Channels
Live market data arrives through channels. Choose an indicator and a target,
attach listeners, and open a feed with velo.watch().
| Part | Purpose |
|---|---|
| Channel | Describes the indicator, target, and listeners for its updates. |
| Feed | Groups channels into one subscription request. |
| Watcher | Manages the connections and the channels being subscribed to. |
Building channels and feeds sends nothing. velo.watch() starts the connections
and resolves to a watcher, while data arrives through each channel's listeners.
import { Velo } from 'velo-sdk';
const velo = new Velo({ apiKey: process.env.VELO_API_KEY! });
const product = {
exchange: 'binance-futures',
coin: 'BTC',
product: 'BTCUSDT',
} as const;
const price = velo.channels.price(product).on({
data: (row) => console.log(row.time, row.close_price),
error: ({ reason }) => console.error('Price channel error:', reason),
});
const openInterest = velo.channels.openInterest({ coin: product.coin }, { metric: 'coins' }).on({
data: (entries) => console.table(entries),
error: ({ reason }) => console.error('Open interest channel error:', reason),
});
const feed = velo.channels.feed([price, openInterest]);
const watcher = await velo.watch(feed, {
on: { error: (error) => console.error('Connection error:', error) },
});Scope
A channel's target determines its scope:
| Scope | Target | Description |
|---|---|---|
| Single | { exchange, coin, product } | Subscribes to one product on one exchange (e.g. BTCUSDT on Binance Futures). |
| Aggregated | { coin } | Subscribes to a coin across exchanges, returning one entry per exchange (e.g. BTC on Binance Futures and Hyperliquid). |
Use the catalog to find products. See the Channels reference for supported scopes and markets.
Listeners
Attach listeners with .on({ data, error }). A data listener is required before
adding a channel to a feed; error is optional. The data is typed to the channel's
indicator, target, and options.
Each indicator field is a number, or null when the server sends no value for
it. Check for null before doing arithmetic with a field.
.on() returns a new channel with the listeners attached. Keep that returned
value to unsubscribe it later.
Message metadata
The data listener receives (data, message): decoded data and a ChannelMessage
with these fields:
| Field | Description |
|---|---|
kind | Identifies the channel's decoder, such as price |
channel | The wire name used for the subscription |
timestamp | The server's tick timestamp in Unix milliseconds, when present |
data | The same decoded data as the first argument |
frame | The complete, unmodified server frame |
Error handling
Subscription and decoding failures go to the channel's error listener.
Connection failures go to the watcher's error listener. A channel error does
not disconnect other channels.
Channel errors include the channel's wire name and a reason:
| Reason | Meaning |
|---|---|
rejected | The server does not recognize the channel name. The channel receives no data until the next connection, which subscribes to it again. |
decode | A frame could not be decoded and was skipped. The event also includes the decoder's error and the frame. |
failing | Three frames in a row could not be decoded. Further failures are not reported until a frame decodes again. The subscription stays open, and data resumes with the next frame that decodes. |
Managing subscriptions
Use watcher.subscribe(channel) and watcher.unsubscribe(channel) to change
channels while the feed is running. Keep the channel returned by .on() to
unsubscribe it later:
const volume = velo.channels.volume(product).on({
data: (row) => console.log(row.buy_dollar_volume, row.sell_dollar_volume),
error: ({ reason }) => console.error('Volume channel error:', reason),
});
watcher.subscribe(volume);
// Later:
watcher.unsubscribe(volume);
watcher.close();unsubscribe() requires the same channel object passed to the feed or
subscribe(). A watcher must keep at least one channel; use close() to stop
the last one.
Connections retry automatically by default. Reconnecting restores the current
subscriptions but does not replay missed updates. See
velo.watch() for connection options.
Understanding update timing
For built-in single channels, time is the start of the current minute in Unix
milliseconds. Updates for the same product and minute share the same time.
Volume, trade counts, and liquidation counts and volumes accumulate within the minute and reset for the next one. Replace stored values with each update to avoid double-counting them.
Aggregated entries contain each exchange's latest values, without time or
product. Exchanges can roll into a new minute at different times, so a message
can contain values from different minutes. Use single channels when you need
rows aligned by minute.