Skip to content

Arrow HFT Datastream API

Real-time market data streaming via WebSocket with binary protocol for high-performance trading applications.

Overview

The Arrow Dataserver API provides institutional-grade real-time market data streaming through persistent WebSocket connections. Built with a binary protocol for maximum efficiency, it supports Last Traded Price (LTP) and Full market depth data across NSE and BSE exchanges.

Key Features

  • Binary Protocol: Optimized binary frames for minimal latency and bandwidth
  • Dual Data Modes: LTPC (compact) or Full (complete market depth)
  • Flexible Symbol Formats: Subscribe using symbol names or numeric IDs
  • Cross-Exchange Coverage: Unified data stream across NSE CM, NSE FO, BSE CM, and BSE FO
  • Configurable Latency: Adjust tick intervals upto 50ms (Super fast)
  • Scalable Architecture: Support for up to 4,096 simultaneous symbol subscriptions

Connection Setup

WebSocket Endpoint

wss://socket.arrow.trade?appID=<APP_ID>&token=<TOKEN>

Connection Parameters

Parameter Value Description
appID APP_ID Application identifier
token TOKEN Authentication token

Establishing Connection

const wsUrl = 'wss://socket.arrow.trade?appID=APP_ID&token=TOKEN';
const ws = new WebSocket(wsUrl);

ws.binaryType = 'arraybuffer';

ws.onopen = function(event) {
    console.log('Connected to Arrow Dataserver');
};

ws.onmessage = function(event) {
    const data = new Uint8Array(event.data);
    const marketData = parseMarketData(data);
    console.log(marketData);
};
import websocket
import struct

ws_url = "wss://socket.arrow.trade?appID=APP_ID&token=TOKEN"

def on_message(ws, message):
    market_data = parse_market_data(message)
    print(market_data)

def on_open(ws):
    print("Connected to Arrow Dataserver")

ws = websocket.WebSocketApp(
    ws_url,
    on_message=on_message,
    on_open=on_open
)
ws.run_forever()
# Test connection using websocat
websocat "wss://socket.arrow.trade?appID=<APP_ID>&token=<TOKEN>"

Subscription Management

Request Format

All subscription requests are sent as JSON strings over the WebSocket connection.

{
  "code": "string",
  "mode": "string",
  "symbols": ["string"],
  "latency": integer
}

Request Fields

Field Required Type Description
code Yes string Action: sub/s (subscribe) or unsub/u (unsubscribe)
mode Yes string Data mode: ltpc/l (compact) or full/f (complete)
symbols Conditional array Symbol names (mutually exclusive with symIds)
symIds Conditional array Symbol ID objects (mutually exclusive with symbols)
latency No integer Tick interval in ms (upto 50, default: 1000)

Data Modes

Mode Shorthand Packet Size Description Best For
ltpc l 40 bytes Last Traded Price Compact Basic price monitoring, high-frequency feeds
full f 192 bytes Complete market data with depth Order book analysis, full market view

Symbol Naming Conventions

Symbol Format Overview

Segment Pattern Example
NSE Cash Market NSE.<UNDERLYING>-<SERIES> NSE.SBIN-EQ
NSE Futures <UNDERLYING><DD><MON><YY>F BANKNIFTY30DEC25F
NSE Options <UNDERLYING><DD><MON><YY><C\|P><STRIKE> NYKAA30DEC25C232.5
BSE Cash Market BSE.<UNDERLYING> BSE.SBIN
BSE Futures <ASSET_TYPE><DD><MON><YY>F SENSEX01JAN26F
BSE Options <ASSET_TYPE><DD><MON><YY><C\|P><STRIKE> SENSEX01JAN26C74900

NSE Cash Market (NSECM)

Format: NSE.<UNDERLYING>-<SERIES>

NSE.SBIN-EQ     → State Bank of India, Equity series
NSE.RELIANCE-EQ → Reliance Industries, Equity series
NSE.TCS-EQ      → Tata Consultancy Services, Equity series

NSE Futures & Options (NSEFO)

Options Format: <UNDERLYING><DD><MON><YY><C|P><STRIKE>

  • C = Call Option
  • P = Put Option
  • Strike prices are in rupees (drop trailing .0 for whole numbers)
NYKAA30DEC25C232.5  → NYKAA Call, 30-Dec-2025 expiry, Strike ₹232.50
NYKAA30DEC25P360    → NYKAA Put, 30-Dec-2025 expiry, Strike ₹360
NIFTY25JAN25C24000  → NIFTY Call, 25-Jan-2025 expiry, Strike ₹24,000

Futures Format: <UNDERLYING><DD><MON><YY>F

BANKNIFTY30DEC25F   → Bank Nifty Future, 30-Dec-2025 expiry
NIFTY25JAN25F       → Nifty Future, 25-Jan-2025 expiry

BSE Cash Market (BSECM)

Format: BSE.<UNDERLYING>

BSE.SBIN      → State Bank of India
BSE.RELIANCE  → Reliance Industries
BSE.HDFC      → HDFC Bank

BSE Futures & Options (BSEFO)

Options Format: <ASSET_TYPE><DD><MON><YY><C|P><STRIKE>

SENSEX01JAN26C74900 → SENSEX Call, 01-Jan-2026 expiry, Strike ₹74,900
SENSEX01JAN26P74000 → SENSEX Put, 01-Jan-2026 expiry, Strike ₹74,000

Futures Format: <ASSET_TYPE><DD><MON><YY>F

SENSEX01JAN26F → SENSEX Future, 01-Jan-2026 expiry

Subscription Examples

Subscribe Using Symbol Names

{
  "code": "sub",
  "mode": "full",
  "symbols": ["NSE.SBIN-EQ", "BSE.RELIANCE"],
  "latency": 200
}

Subscribe Using Symbol IDs

{
  "code": "sub",
  "mode": "full",
  "symIds": [
    {
      "exch_seg": 1,
      "ids": [5042, 4449, 91]
    },
    {
      "exch_seg": 2,
      "ids": [100, 200]
    }
  ]
}

Subscribe to LTPC Mode

{
  "code": "sub",
  "mode": "ltpc",
  "symbols": ["NSE.SBIN-EQ", "NSE.RELIANCE-EQ"],
  "latency": 100
}

Unsubscribe from Symbols

{
  "code": "unsub",
  "mode": "full",
  "symbols": ["NSE.SBIN-EQ"]
}

Using Shorthand Codes

{
  "code": "s",
  "mode": "l",
  "symbols": ["NSE.SBIN-EQ"],
  "l": 500
}

Exchange Segments

Value Segment Description
0 NSE_CM NSE Cash Market
1 NSE_FO NSE Futures & Options
2 BSE_CM BSE Cash Market
3 BSE_FO BSE Futures & Options

Binary Response Format

Response Packet Structure (540 bytes)

After each subscription request, the server sends a binary acknowledgment packet.

Offset Size Type Field Description
0 4 uint32 size Total packet size
4 1 uint8 pktType 99 (PKT_TYPE_RESPONSE)
5 1 uint8 exchSeg 0 (not used)
6 16 char[] error_code Error code string (null-terminated)
22 512 char[] error_msg Error message (null-terminated)
534 1 uint8 request_type 0=subscribe, 1=unsubscribe
535 1 uint8 mode 0=ltpc, 1=full
536 2 uint16 success_count Number of successful symbols
538 2 uint16 error_count Number of failed symbols

Error Codes

Code Description
SUCCESS All symbols processed successfully
E_PARTIAL Some symbols failed
E_ALL_INVALID All symbols failed
E_INVALID_JSON Malformed JSON request
E_MISSING_FIELD Required field missing
E_INVALID_PARAM Invalid parameter value
E_PARSE_ERROR General parsing error

Market Data Packets

LTP Packet (LTPC Mode - 40 bytes)

Compact price data for high-frequency monitoring.

Offset Size Type Field Description
0 2 int16 size Packet size (40)
2 1 uint8 pktType 1 (PKT_TYPE_LTP)
3 1 uint8 exchSeg Exchange segment
4 4 int32 symId Symbol/Token ID
8 4 int32 ltp Last traded price (paise)
12 4 int32 vwap Volume-weighted average price
16 8 int64 volume Traded volume (units)
24 8 uint64 ltt Last traded time (seconds)
32 4 uint32 atv Ask traded volume
36 4 uint32 btv Buy traded volume

Full Packet (Full Mode - 192 bytes)

Complete market data with 5-level order book depth.

Offset Size Type Field Description
0 2 int16 size Packet size (192)
2 1 uint8 pktType 2 (PKT_TYPE_FULL)
3 1 uint8 exchSeg Exchange segment
4 4 int32 token Symbol/Token ID
8 4 int32 ltp Last traded price
12 4 int32 ltq Last traded quantity
16 4 int32 vwap Volume-weighted average price
20 4 int32 open Open price
24 4 int32 high High price
28 4 int32 close Close price
32 4 int32 low Low price
36 4 int32 ltt Last traded time (seconds)
40 4 int32 dpr_l Day price range low
44 4 int32 dpr_h Day price range high
48 8 int64 tbq Total buy quantity
56 8 int64 tsq Total sell quantity
64 8 int64 volume Total volume
72 20 int32[5] bid_px Best 5 bid prices
92 20 int32[5] ask_px Best 5 ask prices
112 20 int32[5] bid_size Best 5 bid quantities
132 20 int32[5] ask_size Best 5 ask quantities
152 10 uint16[5] bid_ord Bid order counts (levels 1-5)
162 10 uint16[5] ask_ord Ask order counts (levels 1-5)
172 4 int32 oi Open interest
176 8 uint64 ts Server timestamp (epoch ns)
184 4 uint32 atv Ask traded volume
188 4 uint32 btv Buy traded volume

Implementation Examples

Complete WebSocket Client

class ArrowDataserverClient {
    constructor() {
        this.wsUrl = 'wss://socket.arrow.trade?appID=APP_ID&token=TOKEN';
        this.ws = null;
        this.callbacks = new Map();
    }

    connect() {
        return new Promise((resolve, reject) => {
            this.ws = new WebSocket(this.wsUrl);
            this.ws.binaryType = 'arraybuffer';

            this.ws.onopen = () => {
                console.log('Connected to Arrow Dataserver');
                resolve();
            };

            this.ws.onmessage = (event) => {
                this.handleMessage(event.data);
            };

            this.ws.onerror = (error) => {
                console.error('WebSocket error:', error);
                reject(error);
            };

            this.ws.onclose = () => {
                console.log('Disconnected from Arrow Dataserver');
                this.reconnect();
            };
        });
    }

    subscribe(mode, symbols, latency, callback) {
        const message = {
            code: 'sub',
            mode: mode,
            symbols: symbols,
            latency: latency || 1000
        };
        this.ws.send(JSON.stringify(message));
        symbols.forEach(sym => this.callbacks.set(sym, callback));
    }

    subscribeByIds(mode, symIds, latency, callback) {
        const message = {
            code: 'sub',
            mode: mode,
            symIds: symIds,
            latency: latency || 1000
        };
        this.ws.send(JSON.stringify(message));
    }

    unsubscribe(mode, symbols) {
        const message = {
            code: 'unsub',
            mode: mode,
            symbols: symbols
        };
        this.ws.send(JSON.stringify(message));
        symbols.forEach(sym => this.callbacks.delete(sym));
    }

    handleMessage(data) {
        const buffer = new DataView(data);
        const size = buffer.getInt16(0, true);

        if (size === 40) {
            const tick = this.parseLTP(buffer);
            this.notifyCallbacks(tick);
        } else if (size === 192) {
            const tick = this.parseFull(buffer);
            this.notifyCallbacks(tick);
        } else if (size === 540) {
            const response = this.parseResponse(buffer);
            console.log('Subscription response:', response);
        }
    }

    parseLTP(buffer) {
        return {
            pktType: buffer.getUint8(2),
            exchSeg: buffer.getUint8(3),
            symId: buffer.getInt32(4, true),
            ltp: buffer.getInt32(8, true) / 100,
            vwap: buffer.getInt32(12, true) / 100,
            volume: Number(buffer.getBigInt64(16, true)),
            ltt: Number(buffer.getBigUint64(24, true)),
            atv: buffer.getUint32(32, true),
            btv: buffer.getUint32(36, true)
        };
    }

    parseFull(buffer) {
        const tick = {
            pktType: buffer.getUint8(2),
            exchSeg: buffer.getUint8(3),
            token: buffer.getInt32(4, true),
            ltp: buffer.getInt32(8, true) / 100,
            ltq: buffer.getInt32(12, true),
            vwap: buffer.getInt32(16, true) / 100,
            open: buffer.getInt32(20, true) / 100,
            high: buffer.getInt32(24, true) / 100,
            close: buffer.getInt32(28, true) / 100,
            low: buffer.getInt32(32, true) / 100,
            ltt: buffer.getInt32(36, true),
            dprLow: buffer.getInt32(40, true) / 100,
            dprHigh: buffer.getInt32(44, true) / 100,
            tbq: Number(buffer.getBigInt64(48, true)),
            tsq: Number(buffer.getBigInt64(56, true)),
            volume: Number(buffer.getBigInt64(64, true)),
            bidPrices: [],
            askPrices: [],
            bidSizes: [],
            askSizes: [],
            bidOrders: [],
            askOrders: [],
            oi: buffer.getInt32(172, true),
            timestamp: Number(buffer.getBigUint64(176, true)),
            atv: buffer.getUint32(184, true),
            btv: buffer.getUint32(188, true)
        };

        // Parse 5-level depth
        for (let i = 0; i < 5; i++) {
            tick.bidPrices.push(buffer.getInt32(72 + i * 4, true) / 100);
            tick.askPrices.push(buffer.getInt32(92 + i * 4, true) / 100);
            tick.bidSizes.push(buffer.getInt32(112 + i * 4, true));
            tick.askSizes.push(buffer.getInt32(132 + i * 4, true));
            tick.bidOrders.push(buffer.getUint16(152 + i * 2, true));
            tick.askOrders.push(buffer.getUint16(162 + i * 2, true));
        }

        return tick;
    }

    parseResponse(buffer) {
        const decoder = new TextDecoder();
        const errorCodeBytes = new Uint8Array(buffer.buffer, 6, 16);
        const errorMsgBytes = new Uint8Array(buffer.buffer, 22, 512);

        return {
            size: buffer.getUint32(0, true),
            pktType: buffer.getUint8(4),
            errorCode: decoder.decode(errorCodeBytes).replace(/\0/g, ''),
            errorMsg: decoder.decode(errorMsgBytes).replace(/\0/g, ''),
            requestType: buffer.getUint8(534) === 0 ? 'subscribe' : 'unsubscribe',
            mode: buffer.getUint8(535) === 0 ? 'ltpc' : 'full',
            successCount: buffer.getUint16(536, true),
            errorCount: buffer.getUint16(538, true)
        };
    }

    notifyCallbacks(tick) {
        this.callbacks.forEach((callback) => {
            callback(tick);
        });
    }

    reconnect() {
        setTimeout(() => {
            console.log('Reconnecting...');
            this.connect();
        }, 5000);
    }
}

// Usage
const client = new ArrowDataserverClient();

await client.connect();

client.subscribe('full', ['NSE.SBIN-EQ', 'NSE.RELIANCE-EQ'], 200, (tick) => {
    console.log(`Token: ${tick.token}, LTP: ₹${tick.ltp}, Volume: ${tick.volume}`);
});
import websocket
import json
import struct
from threading import Thread

class ArrowDataserverClient:
    def __init__(self):
        self.ws_url = "wss://socket.arrow.trade?appID=APP_ID&token=TOKEN"
        self.ws = None
        self.callbacks = {}

    def connect(self):
        self.ws = websocket.WebSocketApp(
            self.ws_url,
            on_message=self.on_message,
            on_error=self.on_error,
            on_close=self.on_close,
            on_open=self.on_open
        )
        wst = Thread(target=self.ws.run_forever)
        wst.daemon = True
        wst.start()

    def on_open(self, ws):
        print("Connected to Arrow Dataserver")

    def on_error(self, ws, error):
        print(f"WebSocket error: {error}")

    def on_close(self, ws, close_status_code, close_msg):
        print("Disconnected from Arrow Dataserver")

    def subscribe(self, mode, symbols, latency=1000, callback=None):
        message = {
            'code': 'sub',
            'mode': mode,
            'symbols': symbols,
            'latency': latency
        }
        self.ws.send(json.dumps(message))
        if callback:
            for sym in symbols:
                self.callbacks[sym] = callback

    def subscribe_by_ids(self, mode, sym_ids, latency=1000):
        message = {
            'code': 'sub',
            'mode': mode,
            'symIds': sym_ids,
            'latency': latency
        }
        self.ws.send(json.dumps(message))

    def unsubscribe(self, mode, symbols):
        message = {
            'code': 'unsub',
            'mode': mode,
            'symbols': symbols
        }
        self.ws.send(json.dumps(message))

    def on_message(self, ws, message):
        data = bytes(message)
        size = struct.unpack('<h', data[0:2])[0]

        if size == 40:
            tick = self.parse_ltp(data)
            self.notify_callbacks(tick)
        elif size == 192:
            tick = self.parse_full(data)
            self.notify_callbacks(tick)
        elif size == 540:
            response = self.parse_response(data)
            print(f"Subscription response: {response}")

    def parse_ltp(self, data):
        return {
            'pkt_type': struct.unpack('<B', data[2:3])[0],
            'exch_seg': struct.unpack('<B', data[3:4])[0],
            'sym_id': struct.unpack('<i', data[4:8])[0],
            'ltp': struct.unpack('<i', data[8:12])[0] / 100,
            'vwap': struct.unpack('<i', data[12:16])[0] / 100,
            'volume': struct.unpack('<q', data[16:24])[0],
            'ltt': struct.unpack('<Q', data[24:32])[0],
            'atv': struct.unpack('<I', data[32:36])[0],
            'btv': struct.unpack('<I', data[36:40])[0]
        }

    def parse_full(self, data):
        tick = {
            'pkt_type': struct.unpack('<B', data[2:3])[0],
            'exch_seg': struct.unpack('<B', data[3:4])[0],
            'token': struct.unpack('<i', data[4:8])[0],
            'ltp': struct.unpack('<i', data[8:12])[0] / 100,
            'ltq': struct.unpack('<i', data[12:16])[0],
            'vwap': struct.unpack('<i', data[16:20])[0] / 100,
            'open': struct.unpack('<i', data[20:24])[0] / 100,
            'high': struct.unpack('<i', data[24:28])[0] / 100,
            'close': struct.unpack('<i', data[28:32])[0] / 100,
            'low': struct.unpack('<i', data[32:36])[0] / 100,
            'ltt': struct.unpack('<i', data[36:40])[0],
            'dpr_low': struct.unpack('<i', data[40:44])[0] / 100,
            'dpr_high': struct.unpack('<i', data[44:48])[0] / 100,
            'tbq': struct.unpack('<q', data[48:56])[0],
            'tsq': struct.unpack('<q', data[56:64])[0],
            'volume': struct.unpack('<q', data[64:72])[0],
            'bid_prices': [],
            'ask_prices': [],
            'bid_sizes': [],
            'ask_sizes': [],
            'bid_orders': [],
            'ask_orders': [],
            'oi': struct.unpack('<i', data[172:176])[0],
            'timestamp': struct.unpack('<Q', data[176:184])[0],
            'atv': struct.unpack('<I', data[184:188])[0],
            'btv': struct.unpack('<I', data[188:192])[0]
        }

        # Parse 5-level depth
        for i in range(5):
            tick['bid_prices'].append(struct.unpack('<i', data[72 + i*4:76 + i*4])[0] / 100)
            tick['ask_prices'].append(struct.unpack('<i', data[92 + i*4:96 + i*4])[0] / 100)
            tick['bid_sizes'].append(struct.unpack('<i', data[112 + i*4:116 + i*4])[0])
            tick['ask_sizes'].append(struct.unpack('<i', data[132 + i*4:136 + i*4])[0])
            tick['bid_orders'].append(struct.unpack('<H', data[152 + i*2:154 + i*2])[0])
            tick['ask_orders'].append(struct.unpack('<H', data[162 + i*2:164 + i*2])[0])

        return tick

    def parse_response(self, data):
        error_code = data[6:22].decode('utf-8').rstrip('\x00')
        error_msg = data[22:534].decode('utf-8').rstrip('\x00')

        return {
            'size': struct.unpack('<I', data[0:4])[0],
            'pkt_type': struct.unpack('<B', data[4:5])[0],
            'error_code': error_code,
            'error_msg': error_msg,
            'request_type': 'subscribe' if data[534] == 0 else 'unsubscribe',
            'mode': 'ltpc' if data[535] == 0 else 'full',
            'success_count': struct.unpack('<H', data[536:538])[0],
            'error_count': struct.unpack('<H', data[538:540])[0]
        }

    def notify_callbacks(self, tick):
        for callback in self.callbacks.values():
            callback(tick)


# Usage
client = ArrowDataserverClient()
client.connect()

def price_handler(tick):
    print(f"Token: {tick['token']}, LTP: ₹{tick['ltp']}, Volume: {tick['volume']}")

import time
time.sleep(1)  # Wait for connection

client.subscribe('full', ['NSE.SBIN-EQ', 'NSE.RELIANCE-EQ'], 200, price_handler)

# Keep running
while True:
    time.sleep(1)

Error Handling and Reconnection

Connection Management

class ReconnectingClient {
    constructor() {
        this.reconnectInterval = 5000;
        this.maxReconnectAttempts = 10;
        this.reconnectAttempts = 0;
        this.subscriptions = [];
    }

    connect() {
        this.ws = new WebSocket('wss://socket.arrow.trade?appID=APP_ID&token=TOKEN');
        this.ws.binaryType = 'arraybuffer';

        this.ws.onopen = () => {
            this.reconnectAttempts = 0;
            this.resubscribeAll();
        };

        this.ws.onclose = () => {
            if (this.reconnectAttempts < this.maxReconnectAttempts) {
                const delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts);
                setTimeout(() => {
                    this.reconnectAttempts++;
                    this.connect();
                }, delay);
            }
        };
    }

    resubscribeAll() {
        this.subscriptions.forEach(sub => {
            this.ws.send(JSON.stringify(sub));
        });
    }
}

Common Error Scenarios

Error Cause Solution
E_INVALID_JSON Malformed JSON request Validate JSON before sending
E_MISSING_FIELD Required field missing Include all required fields
E_INVALID_PARAM Invalid parameter value Check parameter constraints
E_ALL_INVALID All symbols invalid Verify symbol name formats
E_PARTIAL Some symbols failed Check error_msg for details
Connection timeout Network issues Implement reconnection with backoff

Rate Limits and Constraints

Constraint Limit
Requests per second 100 per connection
Maximum symbols per subscription 4,096
Maximum request size 16 KB
Latency range 50ms - 60,000ms

Data Type Notes

Type Description
Prices All prices are in paise (1 rupee = 100 paise)
Timestamps Nanoseconds since Unix epoch
Byte order Little-endian for all multi-byte integers

Best Practices

Subscription Management

  • Batch subscriptions: Subscribe to multiple symbols in a single request to reduce overhead
  • Mode selection: Use ltpc for minimal data when you only need last traded prices; use full for market depth and complete price information
  • Symbol validation: Verify symbol names/IDs before sending requests
  • Latency tuning: Adjust latency based on your application needs (lower for real-time, higher for reduced bandwidth)

Error Handling

  • Connection errors: Reconnect with exponential backoff
  • Invalid symbols: Check the error_msg field for specific invalid symbols
  • Subscription limits: Split large requests into smaller batches
  • Parse errors: Validate JSON format before sending

Performance Optimization

  • Memory management: Parse and process data efficiently to prevent memory leaks
  • Binary parsing: Use optimized binary parsers for high-frequency data
  • Buffer handling: Implement proper buffer management for binary data streams
  • Connection pooling: Reuse connections where possible

Market Data Applications

Real-Time Price Monitoring

function formatTick(tick) {
    const change = ((tick.ltp - tick.close) / tick.close * 100).toFixed(2);
    const direction = tick.ltp >= tick.close ? '▲' : '▼';
    return `${tick.token}: ₹${tick.ltp} ${direction} ${change}%`;
}

Order Book Analysis

function analyzeOrderBook(tick) {
    const bestBid = tick.bidPrices[0];
    const bestAsk = tick.askPrices[0];
    const spread = bestAsk - bestBid;
    const spreadPercent = (spread / bestBid * 100).toFixed(4);

    const totalBidDepth = tick.bidSizes.reduce((a, b) => a + b, 0);
    const totalAskDepth = tick.askSizes.reduce((a, b) => a + b, 0);

    return {
        spread: spread,
        spreadPercent: spreadPercent,
        bidDepth: totalBidDepth,
        askDepth: totalAskDepth,
        imbalance: (totalBidDepth - totalAskDepth) / (totalBidDepth + totalAskDepth)
    };
}

Volume Analysis

function analyzeVolume(tick) {
    return {
        totalVolume: tick.volume,
        buyVolume: tick.btv,
        sellVolume: tick.atv,
        buyRatio: (tick.btv / (tick.btv + tick.atv) * 100).toFixed(2),
        vwap: tick.vwap
    };
}