Portfolio Channel
Real-time portfolio updates including balances and statistics
Overview
The portfolio channel provides real-time updates of your account portfolio, including asset balances, locked amounts, trading fees, and account statistics. Updates are sent whenever any balance or stat changes due to trades, orders, deposits, or withdrawals.
Channel: portfolio
Authentication: Required
Product: Not applicable (all assets included)
Subscribe
You must authenticate before subscribing to the portfolio channel. See Authentication for details.
Request:
{
"op": "sub",
"channel": "portfolio"
}Friendly format:
sub portfolioParameters:
op(string, required) - Must be"sub"channel(string, required) - Must be"portfolio"
Responses
Subscription Confirmation
Upon successful subscription:
{
"channel": "portfolio",
"type": "subscribed"
}Snapshot Message
Immediately after subscription, you'll receive a complete snapshot of your portfolio:
{
"channel": "portfolio",
"type": "snapshot",
"data": [
{
"id": "1000004",
"maker_fee_rate": "0.0001",
"taker_fee_rate": "0.0005",
"asset_balances": [
{
"asset": "VND",
"balance": "850102477.1110636",
"free": "724006849.6121439",
"locked": "126095627.4989197"
},
{
"asset": "BTC",
"balance": "2.130511",
"free": "2.058586",
"locked": "0.071925"
},
{
"asset": "ETH",
"balance": "15.5",
"free": "15.5",
"locked": "0"
}
],
"stats": {
"order_stats": {
"total_orders": "916152",
"total_done_orders": "916105",
"total_open_orders": "46",
"total_pending_orders": "1",
"total_accepted_orders": "0"
},
"trading_stats": {
"total_trading_volume": "99612906669"
}
}
}
],
"timestamp": "1763121358960696000",
"gsn": 105240183
}Update Messages
After the snapshot, you'll receive updates whenever your portfolio changes:
{
"channel": "portfolio",
"type": "update",
"data": [
{
"id": "1000004",
"maker_fee_rate": "0.0001",
"taker_fee_rate": "0.0005",
"asset_balances": [
{
"asset": "VND",
"balance": "850202477.1110636",
"free": "724106849.6121439",
"locked": "126095627.4989197"
},
{
"asset": "BTC",
"balance": "2.130511",
"free": "2.058586",
"locked": "0.071925"
}
],
"stats": {
"order_stats": {
"total_orders": "916153",
"total_done_orders": "916106",
"total_open_orders": "46",
"total_pending_orders": "1",
"total_accepted_orders": "0"
},
"trading_stats": {
"total_trading_volume": "99612906669"
}
}
}
],
"timestamp": "1763121359960696000",
"gsn": 105240184
}Data Structure
interface PortfolioData {
id: string; // Account ID
maker_fee_rate: string; // Maker fee rate (e.g., "0.0001" = 0.01%)
taker_fee_rate: string; // Taker fee rate (e.g., "0.0005" = 0.05%)
asset_balances: AssetBalance[];
stats: AccountStats;
}
interface AssetBalance {
asset: string; // Asset symbol (e.g., "BTC", "VND")
balance: string; // Total balance
free: string; // Available balance
locked: string; // Locked balance
}
interface AccountStats {
order_stats: {
total_orders: string; // Total orders created
total_done_orders: string; // Total completed orders
total_open_orders: string; // Currently open orders
total_pending_orders: string; // Pending orders
total_accepted_orders: string; // Accepted orders
};
trading_stats: {
total_trading_volume: string; // Total trading volume (in VND)
};
}
interface PortfolioMessage {
channel: "portfolio";
type: "snapshot" | "update";
data: PortfolioData[]; // Array (supports future multi-account features)
timestamp: string; // Nanoseconds since epoch
gsn: number; // Global Sequence Number
}Balance Calculations
Understanding the relationship between balance fields:
balance = free + locked
free = balance - locked- balance - Total amount you own of this asset
- free - Amount available for trading or withdrawal
- locked - Amount locked by open orders and pending withdrawals
Update Triggers
Portfolio updates are sent when:
- Orders are created - Locks funds for the order
- Orders are triggered - Delayed orders activate and lock funds
- Orders are filled - Updates balance and free amounts
- Orders are cancelled - Unlocks previously locked funds
- Trades are executed - Updates balances for both assets in the pair
- Deposits occur - Increases balance and free amounts
- Withdrawals are created - Locks funds for the withdrawal
- Withdrawals complete - Reduces balance and unlocks funds
- Order statistics change - Updates total orders, done orders, etc.
- Trading volume changes - Updates total trading volume
Example: Portfolio Tracker
const WebSocket = require('ws');
class PortfolioTracker {
constructor() {
this.portfolio = null;
}
handleMessage(message) {
if (message.type === 'snapshot' || message.type === 'update') {
const newPortfolio = message.data[0]; // First account
if (this.portfolio) {
this.compareAndLog(this.portfolio, newPortfolio);
}
this.portfolio = newPortfolio;
this.displayPortfolio();
}
}
compareAndLog(oldPortfolio, newPortfolio) {
// Check for balance changes
oldPortfolio.asset_balances.forEach(oldAsset => {
const newAsset = newPortfolio.asset_balances.find(
a => a.asset === oldAsset.asset
);
if (newAsset) {
const oldBalance = parseFloat(oldAsset.balance);
const newBalance = parseFloat(newAsset.balance);
const change = newBalance - oldBalance;
if (change !== 0) {
console.log(`💰 Balance Change: ${oldAsset.asset}`);
console.log(` ${change > 0 ? '+' : ''}${change.toFixed(8)}`);
console.log(` New Balance: ${newBalance.toFixed(8)}`);
}
const oldLocked = parseFloat(oldAsset.locked);
const newLocked = parseFloat(newAsset.locked);
const lockedChange = newLocked - oldLocked;
if (lockedChange !== 0) {
console.log(`🔒 Locked Change: ${oldAsset.asset}`);
console.log(` ${lockedChange > 0 ? '+' : ''}${lockedChange.toFixed(8)}`);
}
}
});
// Check for new assets
newPortfolio.asset_balances.forEach(newAsset => {
const oldAsset = oldPortfolio.asset_balances.find(
a => a.asset === newAsset.asset
);
if (!oldAsset) {
console.log(`🆕 New Asset: ${newAsset.asset}`);
console.log(` Balance: ${newAsset.balance}`);
}
});
}
displayPortfolio() {
console.log('\n=== PORTFOLIO ===');
console.log(`Account ID: ${this.portfolio.id}`);
console.log(`Maker Fee: ${(parseFloat(this.portfolio.maker_fee_rate) * 100).toFixed(2)}%`);
console.log(`Taker Fee: ${(parseFloat(this.portfolio.taker_fee_rate) * 100).toFixed(2)}%`);
console.log('\nAsset Balances:');
this.portfolio.asset_balances.forEach(asset => {
console.log(` ${asset.asset}:`);
console.log(` Total: ${asset.balance}`);
console.log(` Free: ${asset.free}`);
console.log(` Locked: ${asset.locked}`);
});
const stats = this.portfolio.stats;
console.log('\nOrder Statistics:');
console.log(` Total Orders: ${stats.order_stats.total_orders}`);
console.log(` Done Orders: ${stats.order_stats.total_done_orders}`);
console.log(` Open Orders: ${stats.order_stats.total_open_orders}`);
console.log('\nTrading Statistics:');
console.log(` Total Volume: ${stats.trading_stats.total_trading_volume} VND`);
}
getAssetBalance(asset) {
const assetBalance = this.portfolio?.asset_balances.find(
a => a.asset === asset
);
return assetBalance || null;
}
getTotalValue(prices) {
// Calculate total portfolio value in VND
let total = 0;
this.portfolio?.asset_balances.forEach(asset => {
if (asset.asset === 'VND') {
total += parseFloat(asset.balance);
} else {
const price = prices[`${asset.asset}-VND`];
if (price) {
total += parseFloat(asset.balance) * price;
}
}
});
return total;
}
}
// Usage
const tracker = new PortfolioTracker();
ws.on('open', async () => {
// Authenticate first
await authenticate(ws);
// Subscribe to portfolio
ws.send(JSON.stringify({
op: 'sub',
channel: 'portfolio'
}));
});
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.channel === 'portfolio') {
tracker.handleMessage(message);
}
});Example: Balance Alert System
class BalanceAlert {
constructor(asset, threshold, type = 'below') {
this.asset = asset;
this.threshold = threshold;
this.type = type; // 'below' or 'above'
this.triggered = false;
}
check(assetBalance) {
if (this.triggered) return false;
const balance = parseFloat(assetBalance.balance);
if (this.type === 'below' && balance < this.threshold) {
this.triggered = true;
return true;
}
if (this.type === 'above' && balance > this.threshold) {
this.triggered = true;
return true;
}
return false;
}
}
// Set up alerts
const alerts = [
new BalanceAlert('VND', 1000000000, 'below'), // Alert if VND < 1B
new BalanceAlert('BTC', 1, 'below'), // Alert if BTC < 1
];
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.channel === 'portfolio' && message.type === 'update') {
const portfolio = message.data[0];
portfolio.asset_balances.forEach(asset => {
alerts.forEach((alert, index) => {
if (alert.asset === asset.asset && alert.check(asset)) {
console.log(`🚨 BALANCE ALERT ${index + 1}!`);
console.log(`${asset.asset} balance is ${alert.type} ${alert.threshold}`);
console.log(`Current balance: ${asset.balance}`);
// Send notification
}
});
});
}
});Unsubscribe
To stop receiving portfolio updates:
Request:
{
"op": "unsub",
"channel": "portfolio"
}Response:
{
"type": "unsubscribed",
"channel": "portfolio"
}Important Notes
- Authentication Required - You must authenticate before subscribing
- Array Format - Data is always an array (supports future multi-account admin features)
- Nanosecond Timestamps - All timestamps are in nanoseconds since Unix epoch
- GSN Tracking - Use Global Sequence Number to ensure message ordering
- Fee Rates - Maker and taker fee rates are decimal strings (e.g., "0.0001" = 0.01%)
- Asset Inclusion - Only assets with non-zero balances are included
- Locked Amounts - Includes funds locked by both orders and pending withdrawals
Use Cases
- Balance Display - Show real-time account balances in your UI
- Portfolio Valuation - Calculate total portfolio value
- Risk Management - Monitor available balances before placing orders
- Balance Alerts - Notify users of low balances
- Trading Bots - Track available funds for automated trading
- Analytics - Track trading volume and order statistics
Best Practices
- Cache Portfolio State - Maintain local portfolio state and apply updates
- Handle Reconnections - Re-subscribe and process snapshot after reconnecting
- Validate Balances - Verify
balance = free + lockedrelationship - Use GSN - Track GSN to detect and handle missed messages
- Combine with Orders - Use orders channel to understand why balances are locked
Related Channels
- Orders Channel - See which orders are locking funds
- Ticker Channel - Get current prices for valuation
- Trading Operations - Create orders using available balances