# accountValueSnapshot

## accountValueSnapshot

{% hint style="info" %}
**💧New endpoint - this endpoint is not a part of original Hyperliquid API and is added by us for builder convenience.**
{% endhint %}

{% hint style="warning" %}

### ⚠️ This is an add-on endpoint - access has to be purchased separately.

{% endhint %}

### Overview

The accountValueSnapshot endpoint provides access to aggregated account value data for all users holding positions in specific collateral tokens. This data includes total account values, long/short notional exposures, and is updated in real-time.

There are two ways to access the data:

1. **Metadata Endpoint (Fast)**: Check for updates without downloading data
2. **Snapshots Endpoint (Heavy)**: Download actual account value snapshot data

**Use Cases:**

* Track total value locked (TVL) per collateral token
* Analyze user distribution and concentration
* Monitor leverage and exposure across the platform
* Build leaderboards and analytics dashboards
* Monitor Portfolio Margin users' cross-collateral health (pm\_ratio, avail per branch)

<details>

<summary>Efficient polling pattern</summary>

To minimize bandwidth and server load, you should:

1. Poll the metadata endpoint to check for updates
2. Only call the snapshots endpoint when data has changed
3. Cache the snapshot ID locally to avoid unnecessary downloads

</details>

<details>

<summary>Dependencies and best practices</summary>

**Dependencies**

**JavaScript**

* **zstd**: For decompression (e.g., `@mongodb-js/zstd`)
* **msgpack**: For decoding (e.g., `msgpack-lite`)

**Python**

* **zstd**: For decompression (e.g., `zstandard`)
* **msgpack**: For decoding (e.g., `msgpack`)
* **aiohttp**: For async HTTP requests

***

✅ **Best Practices**

1. **Request specific tokens** - Don't use `['ALL']` unless you truly need all collateral data.
2. **Handle errors gracefully** - Network issues are common; implement retry logic.
3. **Use appropriate poll intervals** - 10-30 seconds is usually sufficient for account value data.
4. **Monitor bandwidth usage** - Snapshots can be large depending on user count.
5. **Cache locally** - Store snapshots to avoid unnecessary repeated downloads.

</details>

<details>

<summary>Error handling</summary>

* **404**: No snapshots are available yet. Wait and retry.
* **500**: A server error occurred. Retry with exponential backoff.
* **Network errors**: Implement retry logic with jitter to avoid overwhelming the server.
* **Parse errors**: Log the issue and continue using cached data if available.

</details>

### Account Value Snapshot Metadata (Fast)

* **Endpoint**: `POST /info`
* **Purpose**: Check if snapshots have been updated
* **Response Time**: \~1ms

#### Request

```json
{
  "type": "accountValueSnapshotTimestamp"
}
```

#### Response

```json
{
  "snapshot_id": "20240915_state_123456789",
  "timestamp": 1694764800
}
```

{% hint style="info" %}
**Note:** Account value snapshots are generated at the same time as perp snapshots, so they share the same timestamp. This endpoint returns the perp snapshot metadata which applies to account values as well.
{% endhint %}

### Account Value Snapshots (Heavy)

* **Endpoint**: `POST /info`
* **Purpose**: Download account value snapshot data for specific collateral tokens
* **Response Time**: \~100ms+

#### Request

```json
{
  "type": "accountValueSnapshots",
  "collateral_tokens": ["USDC"]
}
```

#### Query Options

**Token-only (gets all DEXes using that collateral):**

* `["USDC"]` → Gets account values for all DEXes that use USDC as collateral
* `["USDE"]` → Gets account values for all DEXes that use USDE as collateral

**Specific DEX:Token combinations:**

* `["hyperliquid:USDC"]` → Only Hyperliquid's USDC collateral accounts
* `["xyz:USDC", "vntls:USDE"]` → Specific DEX:token combinations

**Mixed queries:**

* `["USDC", "xyz:USDE"]` → All USDC collateral across DEXes + specific xyz:USDE

**Practical Query Examples:**

```json
// Get all DEXes using USDC as collateral
{"type": "accountValueSnapshots", "collateral_tokens": ["USDC"]}
// Might return: hyperliquid:USDC, xyz:USDC, vntls:USDC (multiple snapshots)

// Get only Hyperliquid's USDC accounts
{"type": "accountValueSnapshots", "collateral_tokens": ["hyperliquid:USDC"]}
// Returns: Only hyperliquid:USDC (single snapshot)

// Get USDC from all DEXes + specific USDE from xyz only
{"type": "accountValueSnapshots", "collateral_tokens": ["USDC", "xyz:USDE"]}
// Returns: All *:USDC snapshots + xyz:USDE snapshot (multiple snapshots)
```

#### Response Headers

* **Single Snapshot Result**: `x-payload-format: msgpack`, `Content-Encoding: zstd`
* **Multiple Snapshot Results**: `x-payload-format: multi-zstd`, `x-compression: inner-zstd`

**Note:** The response format depends on the number of snapshots found, not the number of tokens requested:

* `["USDC"]` might return multiple snapshots if USDC is used on multiple DEXes
* `["hyperliquid:USDC"]` will return a single snapshot (specific DEX:token combination)

#### Data Format

<details>

<summary>Single Token Response</summary>

**Format**: Raw zstd-compressed MessagePack data

**Decompressed Structure:**

{% code overflow="wrap" %}

```json
[
  "20240915_account_value_123456789",  // Snapshot ID
  "USDC",                               // Collateral token
  {                                     // Users map
    "0x1234...": [25000.50, 15000.00, 5000.00],
    "0x5678...": [100000.00, 50000.00, 30000.00],
    // more users...
  }
]
```

{% endcode %}

ℹ️ **User Data Format**

Each user entry is a tuple. Standard users have 5 values; Portfolio Margin users have 8 values:

**Standard / Unified users (5 values):**

```
account_value          // Total account value in USD
total_long_notional    // Sum of all long position notionals
total_short_notional   // Sum of all short position notionals (absolute value)
is_unified             // Boolean: true if unified account (spot included in account_value)
account_mode           // 0=Standard, 1=DexAbstraction, 2=Unified, 3=PortfolioMargin
```

**Portfolio Margin users (8 values):**

```
account_value          // Per-settlement-token account value (perp equity + spot)
total_long_notional    // Sum of all long position notionals
total_short_notional   // Sum of all short position notionals (absolute value)
is_unified             // Boolean (false for PM users)
account_mode           // 3 (PortfolioMargin)
pm_ratio               // Portfolio margin ratio (max across all branches)
pm_avail_by_branch     // Array of [token_name, available_margin] tuples
pm_total_account_value // Total account value across all collateral tokens
```

{% hint style="info" %}
**Detecting account type:** Check `account_mode` (index 4) to determine the user's margin mode. For Portfolio Margin users (`account_mode == 3`), the tuple contains 3 additional fields at indices 5-7. For backwards compatibility, always check the array length before accessing PM fields.
{% endhint %}

**Example PM user data:**

```json
"0x1234...": [2597.64, 1500.00, 800.00, false, 3, 0.138, [["USDC", 1999.40], ["USDH", 1000.00]], 5788.74]
```

**Alternative Object Format:**

```json
{
  "i": "20240915_account_value_123456789",
  "t": "USDC",
  "u": {
    "0x1234...": {
      "v": 25000.50,
      "l": 15000.00,
      "s": 5000.00,
      "m": false,
      "am": 0
    },
    "0x5678...": {
      "v": 2597.64,
      "l": 1500.00,
      "s": 800.00,
      "m": false,
      "am": 3,
      "pm": 0.138,
      "pa": [["USDC", 1999.40], ["USDH", 1000.00]],
      "pt": 5788.74
    }
  }
}
```

</details>

<details>

<summary>Multiple Tokens Response</summary>

**Format**: Custom binary format with length prefixes

**Binary Structure:**

{% code overflow="wrap" %}

```
[token_count: 4 bytes][length1: 4 bytes][zstd_data1][length2: 4 bytes][zstd_data2]...
```

{% endcode %}

Each zstd\_data segment contains a msgpack-encoded AccountValueSnapshot with the same structure as the single token response.

</details>

### Implementation Examples

**Note**: Make sure to set **HYDROMANCER\_API\_KEY** in your .env file

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const axios = require('axios');
const { decompress } = require('@mongodb-js/zstd');
const msgpack = require('msgpack-lite');
const fs = require('fs').promises;
require('dotenv').config();

class AccountValueSnapshotClient {
    constructor() {
        this.baseUrl = process.env.HYDROMANCER_API_URL || 'https://api.hydromancer.xyz';
        this.apiKey = process.env.HYDROMANCER_API_KEY;

        if (!this.apiKey || this.apiKey === 'INSERT_API_KEY_HERE') {
            throw new Error('API key not found. Please set HYDROMANCER_API_KEY in .env file');
        }

        this.cachedSnapshots = {};
        this.cachedTimestamp = null;
        this.outputFile = 'account_value_snapshot.json';
    }

    async checkForUpdates() {
        const headers = {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.apiKey}`
        };

        try {
            const response = await axios.post(
                `${this.baseUrl}/info`,
                { type: 'accountValueSnapshotTimestamp' },
                { headers }
            );

            const currentTimestamp = response.data.timestamp || response.data.snapshot_id;
            const hasUpdates = this.cachedTimestamp !== currentTimestamp;

            if (hasUpdates) {
                console.log(`Account value snapshot updated: ${this.cachedTimestamp} -> ${currentTimestamp}`);
                this.cachedTimestamp = currentTimestamp;
            } else {
                console.log(`No account value snapshot updates (timestamp: ${currentTimestamp})`);
            }

            return hasUpdates;
        } catch (error) {
            throw new Error(`Timestamp request failed: ${error.response?.status} - ${error.response?.data || error.message}`);
        }
    }

    async pollSnapshots(collateralTokens = ['USDC']) {
        try {
            const hasUpdates = await this.checkForUpdates();

            if (!hasUpdates) {
                console.log('No updates available, using cached data');
                return this.cachedSnapshots;
            }

            console.log('New snapshots available, downloading...');
            const snapshots = await this.downloadSnapshots(collateralTokens);

            this.cachedSnapshots = snapshots;
            await this.saveToFile(snapshots);

            return snapshots;
        } catch (error) {
            console.error('Error polling account value snapshots:', error.message);
            throw error;
        }
    }

    async downloadSnapshots(collateralTokens) {
        const headers = {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.apiKey}`
        };

        try {
            const response = await axios.post(
                `${this.baseUrl}/info`,
                {
                    type: 'accountValueSnapshots',
                    collateral_tokens: collateralTokens
                },
                {
                    headers,
                    responseType: 'arraybuffer'
                }
            );

            const payloadFormat = response.headers['x-payload-format'];
            const binaryData = Buffer.from(response.data);

            console.log(`Payload format: ${payloadFormat}`);
            console.log(`Downloaded ${binaryData.length} bytes`);

            if (payloadFormat === 'multi-zstd') {
                return await this.parseMultipleTokens(binaryData);
            } else {
                return await this.parseSingleToken(binaryData, collateralTokens[0]);
            }
        } catch (error) {
            if (error.response) {
                throw new Error(`Request failed: ${error.response.status} - ${error.response.statusText}`);
            }
            throw error;
        }
    }

    async parseSingleToken(binaryData, tokenName) {
        try {
            // Decompress zstd data
            let decompressed;
            try {
                decompressed = await decompress(binaryData);
                console.log('Successfully decompressed single token data');
            } catch (e) {
                // If decompression fails, assume it's already uncompressed
                decompressed = binaryData;
            }

            // Decode msgpack
            const data = msgpack.decode(decompressed);

            // Array format: [snapshot_id, token, users_map]
            if (Array.isArray(data) && data.length >= 3) {
                const [snapshotId, token, usersMap] = data;
                return {
                    [token]: {
                        snapshot_id: snapshotId,
                        token: token,
                        users: this.parseUsers(usersMap),
                        total_users: Object.keys(usersMap).length
                    }
                };
            }

            // Object format: {i: snapshot_id, t: token, u: users}
            if (data.i !== undefined && data.t !== undefined && data.u !== undefined) {
                return {
                    [data.t]: {
                        snapshot_id: data.i,
                        token: data.t,
                        users: this.parseUsers(data.u),
                        total_users: Object.keys(data.u).length
                    }
                };
            }

            throw new Error('Unknown single token data format');
        } catch (error) {
            console.error('Error parsing single token:', error);
            throw error;
        }
    }

    async parseMultipleTokens(binaryData) {
        let offset = 0;

        // Read number of snapshots (4 bytes, little-endian)
        const count = binaryData.readUInt32LE(offset);
        offset += 4;

        console.log(`Number of account value snapshots: ${count}`);

        const tokens = {};

        for (let i = 0; i < count; i++) {
            // Read length of this snapshot (4 bytes, little-endian)
            const length = binaryData.readUInt32LE(offset);
            offset += 4;

            console.log(`Snapshot ${i + 1}/${count}: ${length} bytes`);

            // Extract and decompress snapshot
            const zstdData = binaryData.slice(offset, offset + length);

            try {
                const decompressed = await decompress(zstdData);
                const data = msgpack.decode(decompressed);

                // Array format: [snapshot_id, token, users_map]
                if (Array.isArray(data) && data.length >= 3) {
                    const [snapshotId, token, usersMap] = data;
                    tokens[token] = {
                        snapshot_id: snapshotId,
                        token: token,
                        users: this.parseUsers(usersMap),
                        total_users: Object.keys(usersMap).length
                    };
                    console.log(`Parsed token: ${token} with ${Object.keys(usersMap).length} users`);
                }
                // Object format: {i, t, u}
                else if (data.i !== undefined && data.t !== undefined && data.u !== undefined) {
                    tokens[data.t] = {
                        snapshot_id: data.i,
                        token: data.t,
                        users: this.parseUsers(data.u),
                        total_users: Object.keys(data.u).length
                    };
                    console.log(`Parsed token: ${data.t} with ${Object.keys(data.u).length} users`);
                }
            } catch (error) {
                console.error(`Error parsing snapshot ${i + 1}:`, error);
            }

            offset += length;
        }

        return tokens;
    }

    parseUsers(usersMap) {
        const userList = [];

        for (const [address, userData] of Object.entries(usersMap)) {
            // Array format: [account_value, total_long_notional, total_short_notional]
            if (Array.isArray(userData) && userData.length >= 3) {
                userList.push({
                    address: address,
                    account_value: userData[0],
                    total_long_notional: userData[1],
                    total_short_notional: userData[2]
                });
            }
            // Object format: {v, l, s}
            else if (userData.v !== undefined) {
                userList.push({
                    address: address,
                    account_value: userData.v,
                    total_long_notional: userData.l || 0,
                    total_short_notional: userData.s || 0
                });
            }
        }

        // Sort by account value descending
        userList.sort((a, b) => b.account_value - a.account_value);

        return userList;
    }

    async saveToFile(snapshots) {
        const outputData = {
            timestamp: new Date().toISOString(),
            tokens: {}
        };

        for (const [token, data] of Object.entries(snapshots)) {
            const users = data.users || [];

            // Calculate statistics
            const totalAccountValue = users.reduce((sum, u) => sum + u.account_value, 0);
            const totalLongNotional = users.reduce((sum, u) => sum + u.total_long_notional, 0);
            const totalShortNotional = users.reduce((sum, u) => sum + u.total_short_notional, 0);

            outputData.tokens[token] = {
                snapshot_id: data.snapshot_id,
                total_users: users.length,
                statistics: {
                    total_account_value: totalAccountValue,
                    total_long_notional: totalLongNotional,
                    total_short_notional: totalShortNotional,
                    avg_account_value: users.length > 0 ? totalAccountValue / users.length : 0
                },
                top_10_users: users.slice(0, 10),
                // all_users: users  // Uncomment to save all users
            };
        }

        await fs.writeFile(this.outputFile, JSON.stringify(outputData, null, 2));
        console.log(`\nSaved account value snapshot to ${this.outputFile}`);

        // Print summary
        for (const [token, data] of Object.entries(outputData.tokens)) {
            const stats = data.statistics;
            console.log(`\nToken: ${token}`);
            console.log(`  Total users: ${data.total_users}`);
            console.log(`  Total account value: $${stats.total_account_value.toLocaleString()}`);
            console.log(`  Total long notional: $${stats.total_long_notional.toLocaleString()}`);
            console.log(`  Total short notional: $${stats.total_short_notional.toLocaleString()}`);
            console.log(`  Average account value: $${stats.avg_account_value.toLocaleString()}`);
        }
    }
}

// Example usage - Continuous polling with timestamp check
async function main() {
    const client = new AccountValueSnapshotClient();

    console.log('Starting continuous polling for USDC account values...');
    console.log('Checking for updates every 5 seconds');
    console.log('Press Ctrl+C to stop\n');

    let pollCount = 0;

    while (true) {
        try {
            pollCount++;
            console.log(`\n[${'='.repeat(50)}]`);
            console.log(`Poll #${pollCount} at ${new Date().toISOString()}`);
            console.log(`[${'='.repeat(50)}]`);

            // pollSnapshots automatically checks timestamp first and only downloads if updated
            const snapshots = await client.pollSnapshots(['USDC']);

            // Log results
            for (const [token, data] of Object.entries(snapshots)) {
                console.log(`\n${token}:`);
                console.log(`  Total users: ${data.users?.length || 0}`);
                console.log(`  Snapshot ID: ${data.snapshot_id}`);

                if (data.users && data.users.length > 0) {
                    const totalValue = data.users.reduce((sum, u) => sum + u.account_value, 0);
                    console.log(`  Total account value: $${totalValue.toLocaleString(undefined, {maximumFractionDigits: 2})}`);
                }
            }

            // Wait 5 seconds before next poll
            console.log('\nWaiting 15 seconds before next check...');
            await new Promise(resolve => setTimeout(resolve, 15000));

        } catch (error) {
            console.error('\nPolling error:', error.message);
            console.log('Retrying in 5 seconds...');
            await new Promise(resolve => setTimeout(resolve, 5000));
        }
    }
}

if (require.main === module) {
    main().catch(console.error);
}

module.exports = AccountValueSnapshotClient;

```

{% endtab %}

{% tab title="Python" %}

```python
import asyncio
import aiohttp
import struct
from typing import Optional, Dict, List, Any
import time
import json
import zstandard as zstd
import msgpack
import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()


class AccountValueSnapshotClient:
    def __init__(self):
        self.base_url = os.getenv('HYDROMANCER_API_URL', 'https://api.hydromancer.xyz')
        self.api_key = os.getenv('HYDROMANCER_API_KEY')

        if not self.api_key or self.api_key == 'INSERT_API_KEY_HERE':
            raise ValueError("API key not found. Please set HYDROMANCER_API_KEY in .env file")

        self.cached_snapshots: Dict[str, Any] = {}
        self.cached_timestamp: Optional[str] = None
        self.session: Optional[aiohttp.ClientSession] = None
        self.output_file = "account_value_snapshot.json"

    async def __aenter__(self):
        self.session = aiohttp.ClientSession()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.session:
            await self.session.close()

    async def check_for_updates(self) -> bool:
        """Check if snapshots have been updated since last poll"""
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.api_key}"
        }

        async with self.session.post(
            f'{self.base_url}/info',
            json={'type': 'accountValueSnapshotTimestamp'},
            headers=headers
        ) as response:
            if not response.ok:
                text = await response.text()
                raise Exception(f'Timestamp request failed: {response.status} - {text}')

            data = await response.json()
            current_timestamp = data.get('timestamp', data.get('snapshot_id'))

            has_updates = self.cached_timestamp != current_timestamp

            if has_updates:
                print(f'Account value snapshot updated: {self.cached_timestamp} -> {current_timestamp}')
                self.cached_timestamp = current_timestamp
            else:
                print(f'No account value snapshot updates (timestamp: {current_timestamp})')

            return has_updates

    async def poll_snapshots(self, collateral_tokens: List[str] = ["USDC"]) -> Dict[str, Any]:
        """Efficiently poll for account value snapshots"""
        try:
            has_updates = await self.check_for_updates()

            if not has_updates:
                print('No updates available, using cached data')
                return self.cached_snapshots

            print('New snapshots available, downloading...')
            snapshots = await self.download_snapshots(collateral_tokens)

            self.cached_snapshots = snapshots
            await self.save_to_file(snapshots)

            return snapshots
        except Exception as error:
            print(f'Error polling account value snapshots: {error}')
            raise

    async def download_snapshots(self, collateral_tokens: List[str]) -> Dict[str, Any]:
        """Download account value snapshots"""
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.api_key}"
        }

        async with self.session.post(
            f'{self.base_url}/info',
            json={'type': 'accountValueSnapshots', 'collateral_tokens': collateral_tokens},
            headers=headers
        ) as response:
            if not response.ok:
                text = await response.text()
                raise Exception(f'Request failed: {response.status} - {text}')

            payload_format = response.headers.get('x-payload-format')
            binary_data = await response.read()

            print(f"Payload format: {payload_format}")
            print(f"Downloaded {len(binary_data)} bytes")

            if payload_format == 'multi-zstd':
                return await self.parse_multiple_tokens(binary_data)
            else:
                return await self.parse_single_token(binary_data, collateral_tokens[0] if len(collateral_tokens) == 1 else None)

    async def parse_single_token(self, binary_data: bytes, token_name: Optional[str] = None) -> Dict[str, Any]:
        """Parse single token response"""
        try:
            # Decompress zstd data
            try:
                decompressed = self.decompress_zstd(binary_data)
                print('Successfully decompressed single token data')
            except:
                decompressed = binary_data

            # Decode msgpack
            data = msgpack.unpackb(decompressed, raw=False)

            # Array format: [snapshot_id, token, users_map]
            if isinstance(data, list) and len(data) >= 3:
                snapshot_id, token, users_map = data[0], data[1], data[2]
                return {
                    token: {
                        'snapshot_id': snapshot_id,
                        'token': token,
                        'users': self.parse_users(users_map),
                        'total_users': len(users_map)
                    }
                }

            # Object format: {i, t, u}
            if isinstance(data, dict) and 'i' in data and 't' in data and 'u' in data:
                return {
                    data['t']: {
                        'snapshot_id': data['i'],
                        'token': data['t'],
                        'users': self.parse_users(data['u']),
                        'total_users': len(data['u'])
                    }
                }

            raise ValueError('Unknown single token data format')
        except Exception as error:
            print(f'Error parsing single token: {error}')
            raise

    async def parse_multiple_tokens(self, binary_data: bytes) -> Dict[str, Any]:
        """Parse multiple tokens response"""
        offset = 0

        # Read number of snapshots
        count = struct.unpack('<I', binary_data[offset:offset + 4])[0]
        offset += 4

        print(f"Number of snapshots: {count}")

        tokens = {}

        for i in range(count):
            # Read length
            length = struct.unpack('<I', binary_data[offset:offset + 4])[0]
            offset += 4

            print(f"Snapshot {i + 1}/{count}: {length} bytes")

            # Extract and decompress
            zstd_data = binary_data[offset:offset + length]

            try:
                decompressed = self.decompress_zstd(zstd_data)
                data = msgpack.unpackb(decompressed, raw=False)

                # Array format
                if isinstance(data, list) and len(data) >= 3:
                    snapshot_id, token, users_map = data[0], data[1], data[2]
                    tokens[token] = {
                        'snapshot_id': snapshot_id,
                        'token': token,
                        'users': self.parse_users(users_map),
                        'total_users': len(users_map)
                    }
                    print(f"Parsed token: {token} with {len(users_map)} users")

                # Object format
                elif isinstance(data, dict) and 'i' in data and 't' in data and 'u' in data:
                    token = data['t']
                    tokens[token] = {
                        'snapshot_id': data['i'],
                        'token': token,
                        'users': self.parse_users(data['u']),
                        'total_users': len(data['u'])
                    }
                    print(f"Parsed token: {token} with {len(data['u'])} users")

            except Exception as e:
                print(f"Error parsing snapshot {i + 1}: {e}")

            offset += length

        return tokens

    def parse_users(self, users_map: Dict) -> List[Dict[str, Any]]:
        """Parse users data from msgpack format"""
        user_list = []

        for address, user_data in users_map.items():
            # Array format: [account_value, total_long_notional, total_short_notional]
            if isinstance(user_data, list) and len(user_data) >= 3:
                user_list.append({
                    'address': address,
                    'account_value': user_data[0],
                    'total_long_notional': user_data[1],
                    'total_short_notional': user_data[2]
                })
            # Object format: {v, l, s}
            elif isinstance(user_data, dict):
                user_list.append({
                    'address': address,
                    'account_value': user_data.get('v', 0),
                    'total_long_notional': user_data.get('l', 0),
                    'total_short_notional': user_data.get('s', 0)
                })

        # Sort by account value descending
        user_list.sort(key=lambda x: x['account_value'], reverse=True)

        return user_list

    def decompress_zstd(self, data: bytes) -> bytes:
        """Decompress zstd data"""
        dctx = zstd.ZstdDecompressor()
        return dctx.decompress(data)

    async def save_to_file(self, snapshots: Dict[str, Any]):
        """Save snapshots to JSON file"""
        output_data = {
            'timestamp': time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime()),
            'tokens': {}
        }

        for token, data in snapshots.items():
            users = data.get('users', [])

            # Calculate statistics
            total_account_value = sum(u['account_value'] for u in users)
            total_long_notional = sum(u['total_long_notional'] for u in users)
            total_short_notional = sum(u['total_short_notional'] for u in users)

            output_data['tokens'][token] = {
                'snapshot_id': data.get('snapshot_id'),
                'total_users': len(users),
                'statistics': {
                    'total_account_value': total_account_value,
                    'total_long_notional': total_long_notional,
                    'total_short_notional': total_short_notional,
                    'avg_account_value': total_account_value / len(users) if users else 0
                },
                'top_10_users': users[:10],
                # 'all_users': users  # Uncomment to save all users
            }

        with open(self.output_file, 'w') as f:
            json.dump(output_data, f, indent=2)

        print(f"\nSaved account value snapshot to {self.output_file}")

        # Print summary
        for token, data in output_data['tokens'].items():
            stats = data['statistics']
            print(f"\nToken: {token}")
            print(f"  Total users: {data['total_users']}")
            print(f"  Total account value: ${stats['total_account_value']:,.2f}")
            print(f"  Total long notional: ${stats['total_long_notional']:,.2f}")
            print(f"  Total short notional: ${stats['total_short_notional']:,.2f}")
            print(f"  Average account value: ${stats['avg_account_value']:,.2f}")


async def main():
    """Example usage - Continuous polling with timestamp check"""
    async with AccountValueSnapshotClient() as client:
        print('Starting continuous polling for USDC account values...')
        print('Checking for updates every 5 seconds')
        print('Press Ctrl+C to stop\n')

        poll_count = 0

        while True:
            try:
                poll_count += 1
                print(f"\n[{'=' * 50}]")
                print(f"Poll #{poll_count} at {time.strftime('%Y-%m-%d %H:%M:%S UTC', time.gmtime())}")
                print(f"[{'=' * 50}]")

                # poll_snapshots automatically checks timestamp first and only downloads if updated
                snapshots = await client.poll_snapshots(['USDC'])

                # Log results
                for token, data in snapshots.items():
                    print(f"\n{token}:")
                    print(f"  Total users: {len(data.get('users', []))}")
                    print(f"  Snapshot ID: {data.get('snapshot_id')}")

                    users = data.get('users', [])
                    if users:
                        total_value = sum(u['account_value'] for u in users)
                        print(f"  Total account value: ${total_value:,.2f}")

                # Wait 5 seconds before next poll
                print('\nWaiting 15 seconds before next check...')
                await asyncio.sleep(15)

            except Exception as error:
                print(f'\nPolling error: {error}')
                print('Retrying in 5 seconds...')
                await asyncio.sleep(5)


if __name__ == "__main__":
    asyncio.run(main())

```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.hydromancer.xyz/readme/rest-api/market-data/accountvaluesnapshot.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
