# WebSocket Market Data API

Real-time market data streaming with ultra-low latency for institutional-grade trading applications. Get live price feeds, market depth, and trading analytics through persistent WebSocket connections.

## Overview

The WebSocket Market Data API delivers professional-grade real-time market data with sub-second latency, making it essential for algorithmic trading, portfolio management platforms, and advanced trading applications requiring instant market updates.

### Key Features

- **Ultra-Low Latency**: Sub-second market data updates with optimized binary protocols
- **Multiple Data Modes**: Choose from LTP, LTPC, Quote, or Full market depth based on your needs
- **Efficient Binary Protocol**: Compact data packets (13–265 bytes) for minimal bandwidth usage
- **Real-Time Market Depth**: Complete order book with 5-level bid/ask data
- **Cross-Exchange Support**: Unified data stream across NSE, BSE, NFO, BFO, and MCXFO

!!! note "MCX tokens"
    Resolve commodity instrument tokens from `GET /mcx`, then subscribe on `wss://ds.arrow.trade` by token ID (the token stream is exchange-agnostic). Order/quote REST APIs use exchange code `MCXFO`.
- **Scalable Architecture**: Handle thousands of instrument subscriptions simultaneously

## Connection Setup

### WebSocket Endpoint
```
wss://ds.arrow.trade
```

### Authentication Parameters
Connect using your application credentials as query parameters:

#### JavaScript

=== "Bash"
    ```bash
    # Test connection
    websocat "wss://ds.arrow.trade?appID=<YOUR_APP_ID>&token=<YOUR_TOKEN>"
    ```

=== "Javascript"

    ```javascript
    const wsUrl = `wss://ds.arrow.trade?appID=${YOUR_APP_ID}&token=${YOUR_TOKEN}`;
    const ws = new WebSocket(wsUrl);

    ws.onopen = function(event) {
        console.log('WebSocket connected');
    };

    ws.onmessage = function(event) {
        // Handle binary market data
        const data = new Uint8Array(event.data);
        const marketData = parseMarketData(data);
    };
    ```

=== "Python"
    ```python
    import websocket
    import struct

    ws_url = f"wss://ds.arrow.trade?appID={YOUR_APP_ID}&token={YOUR_TOKEN}"

    def on_message(ws, message):
        # Parse binary market data
        market_data = parse_market_data(message)
        
    def on_open(ws):
        print("WebSocket connected")
        
    ws = websocket.WebSocketApp(ws_url,
                            on_message=on_message,
                            on_open=on_open)
    ws.run_forever()
    ```

## Subscription Management

### Message Format
All subscription messages follow this JSON structure:

```json
{
    "code": "SUBSCRIPTION_MODE",
    "mode": "DATA_MODE", 
    "DATA_MODE": [array_of_tokens]
}
```

### Subscription Modes

| Mode | Description | Use Case |
|------|-------------|----------|
| `sub` | Subscribe to instruments | Start receiving data for specified tokens |
| `unsub` | Unsubscribe from instruments | Stop data feed for specified tokens |

### Data Modes

| Mode | Description | Packet Size | Update Frequency | Best For |
|------|-------------|-------------|------------------|-----------|
| `ltpc` | Last Traded Price + Previous Close | 17 bytes (33 during CAS) | Real-time | Basic price monitoring |
| `ltp` | Last Traded Price only | 13 bytes (29 during CAS) | Real-time | High-frequency price feeds |
| `quote` | Complete quote without depth | 93 bytes (109 during CAS) | Real-time | Comprehensive market view |
| `full` | Complete data with market depth | 249 bytes (265 during CAS) | Real-time | Order book analysis |

## Closing Auction Session (CAS)

### What is CAS?

The **Closing Auction Session (CAS)** is a **20-minute auction** run after regular market hours for stocks that are available in the **Futures & Options (F&O)** segment. Instead of using the last traded price as the close, eligible buy and sell orders are matched at a **single equilibrium price**, which becomes the **official closing price**.

### Applicable stocks

CAS applies **only** to equities that are available in the **F&O segment** on NSE/BSE. All other equity stocks continue with the existing market closing process (non-CAS close).

On the Data Stream, CAS wire fields (`imbalanceQty`, `indicativeClose`, `refPrice`) are meaningful for CAS-enabled symbols during the auction window; for other instruments they typically remain `0`.

### Session timeline (IST)

| Time | Activity |
|------|----------|
| **9:15 AM – 3:15 PM** | Regular trading session |
| **3:15 PM – 3:20 PM** | Transition period. Orders are accepted and forwarded to the exchange at **3:20 PM** |
| **3:20 PM – 3:30 PM** | Closing Auction Session. Limit and market orders may be placed, modified, or cancelled. The auction closes randomly between **3:28 PM** and **3:30 PM** |
| **3:30 PM – 3:35 PM** | Order matching and determination of the official closing price |
| **3:35 PM** | Trading closes for CAS-enabled stocks |
| **3:50 PM – 4:00 PM** | Post-close session at the official closing price |

### CAS fields on the Data Stream

From **3:15 PM IST**, every subscription mode includes these extra **16 bytes** (no subscription change required):

| Bytes | Field | Type | Notes |
|-------|-------|------|-------|
| 0–8 | Imbalance Quantity | `int64` | **Signed** (negative = sell-side imbalance) |
| 8–12 | Indicative Close | `int32` | Indicative closing / equilibrium price during the auction |
| 12–16 | Reference Price | `int32` | Reference price used in the auction |

| Mode | Before 3:15 PM | From 3:15 PM |
|------|----------------|--------------|
| `ltp` | 13 | **29** |
| `ltpc` | 17 | **33** |
| `quote` | 93 | **109** |
| `full` | 249 | **265** |

Before 3:15 PM, these fields are not present — use `0` for `imbalanceQty`, `indicativeClose`, and `refPrice`.

## Subscription Examples

### Subscribe to Basic Price Data (LTPC)
```json
{
    "code": "sub",
    "mode": "ltpc", 
    "ltpc": [26009, 26000, 256265]
}
```

### Subscribe to High-Frequency Price Feed (LTP)
```json
{
    "code": "sub",
    "mode": "ltp",
    "ltp": [26009, 26000]
}
```

### Subscribe to Complete Market Data (Quote)
```json
{
    "code": "sub", 
    "mode": "quote",
    "quote": [26009, 26000, 256265]
}
```

### Subscribe to Full Market Depth
```json
{
    "code": "sub",
    "mode": "full", 
    "full": [26009, 26000]
}
```

### Unsubscribe from Instruments
```json
{
    "code": "unsub",
    "mode": "ltpc",
    "ltpc": [26009]
}
```

## Binary Data Structures

All market data is received as binary packets for maximum efficiency. Parse using the appropriate structure based on your subscription mode.

### LTPC Mode (17 bytes / 33 bytes during CAS)

Enhanced LTP data with previous day closing price for accurate P&L calculations.

| Bytes | Field | Type | Description |
|-------|-------|------|-------------|
| 0-4 | Token | `int32` | Unique instrument identifier |
| 4-8 | LTP | `int32` | Last traded price (scaled) |
| 8-9 | Net Change Indicator | `int8` | Direction: +1 (up), -1 (down), 0 (unchanged) |
| 9-13 | Net Change | `int32` | Absolute price change from previous close |
| 13-17 | Previous Close | `int32` | Previous trading session close price |
| 17-25 | Imbalance Quantity | `int64` | CAS only — signed imbalance (33-byte packets) |
| 25-29 | Indicative Close | `int32` | CAS only |
| 29-33 | Reference Price | `int32` | CAS only |

> **Note:** LTPC mode includes previous day closing price, enabling accurate net change calculations based on closing price rather than settlement price.

### LTP Mode (13 bytes / 29 bytes during CAS)

Minimal last-traded-price feed.

| Bytes | Field | Type | Description |
|-------|-------|------|-------------|
| 0-4 | Token | `int32` | Unique instrument identifier |
| 4-8 | LTP | `int32` | Last traded price (scaled) |
| 8-13 | Reserved | `bytes` | Reserved / padding |
| 13-21 | Imbalance Quantity | `int64` | CAS only — signed imbalance (29-byte packets) |
| 21-25 | Indicative Close | `int32` | CAS only |
| 25-29 | Reference Price | `int32` | CAS only |

### Complete Market Data Structure (Quote Mode - 93 bytes / 109 bytes during CAS)

Comprehensive market data including OHLC, volume, and open interest.

| Bytes | Field | Type | Description |
|-------|-------|------|-------------|
| 0-4 | Token | `int32` | Instrument identifier |
| 4-8 | LTP | `int32` | Last traded price |
| 8-9 | Net Change Indicator | `int8` | Price direction |
| 9-13 | Net Change | `int32` | Price change amount |
| 13-17 | LTQ | `int32` | Last traded quantity |
| 17-21 | Average Price | `int32` | Volume-weighted average price |
| 21-29 | Total Buy Quantity | `int64` | Cumulative buy orders |
| 29-37 | Total Sell Quantity | `int64` | Cumulative sell orders |
| 37-41 | Open | `int32` | Session opening price |
| 41-45 | High | `int32` | Session highest price |
| 45-49 | Close | `int32` | Previous session close |
| 49-53 | Low | `int32` | Session lowest price |
| 53-61 | Volume | `int64` | Total traded volume |
| 61-65 | LTT | `int32` | Last trade time (epoch) |
| 65-69 | Exchange Time | `int32` | Exchange timestamp |
| 69-77 | Open Interest | `int64` | Current open interest |
| 77-85 | OI Day High | `int64` | Highest OI during session |
| 85-93 | OI Day Low | `int64` | Lowest OI during session |
| 93-101 | Imbalance Quantity | `int64` | CAS only — signed imbalance (109-byte packets) |
| 101-105 | Indicative Close | `int32` | CAS only |
| 105-109 | Reference Price | `int32` | CAS only |

!!! note "Futures volume vs HFT (TBT) feed"
    This Data Stream uses exchange **bcast** data. For **futures (FO)** instruments, `volume` here can be **higher** than on the [HFT Data Stream](../rest-api/hft-stream.md#market-data-packets), which is built from **TBT** data and counts only trades on the subscribed instrument token. Bcast aggregates may include volume from the **spread order book** across related legs. Compare feeds only after accounting for this difference.

### Full Market Data Structure (249 bytes / 265 bytes during CAS)

Complete market data with 5-level order book depth.

| Bytes | Field | Type | Description |
|-------|-------|------|-------------|
| 0-93 | Base Market Data | `bytes` | Same structure as Quote mode |
| 93-97 | Lower Circuit | `int32` | Lower price limit |
| 97-101 | Upper Circuit | `int32` | Upper price limit |
| 101-109 | Reserved | `bytes` | Reserved (8 bytes) |
| 109-249 | Market Depth | `bytes` | Order book data (5 bids + 5 asks) |
| 249-257 | Imbalance Quantity | `int64` | CAS imbalance quantity (**signed**; negative = sell-side imbalance). 265-byte packets only |
| 257-261 | Indicative Close | `int32` | CAS indicative closing price (265-byte packets only) |
| 261-265 | Reference Price | `int32` | CAS reference price (265-byte packets only) |

## Market Depth Structure (Full Mode)

The market depth provides a complete 5-level order book with bid/ask prices, quantities, and order counts.

### Depth Layout (140 bytes total, starting at offset 109)

Each depth level contains 14 bytes:
- **Quantity**: 8 bytes (`int64`)
- **Price**: 4 bytes (`int32`) 
- **Orders**: 2 bytes (`int16`)

| Position | Level | Data | Structure |
|----------|-------|------|-----------|
| 109-123 | Bid 1 | Best buy orders | Quantity (8) + Price (4) + Orders (2) |
| 123-137 | Bid 2 | Second best buy | Quantity (8) + Price (4) + Orders (2) |
| 137-151 | Bid 3 | Third best buy | Quantity (8) + Price (4) + Orders (2) |
| 151-165 | Bid 4 | Fourth best buy | Quantity (8) + Price (4) + Orders (2) |
| 165-179 | Bid 5 | Fifth best buy | Quantity (8) + Price (4) + Orders (2) |
| 179-193 | Ask 1 | Best sell orders | Quantity (8) + Price (4) + Orders (2) |
| 193-207 | Ask 2 | Second best sell | Quantity (8) + Price (4) + Orders (2) |
| 207-221 | Ask 3 | Third best sell | Quantity (8) + Price (4) + Orders (2) |
| 221-235 | Ask 4 | Fourth best sell | Quantity (8) + Price (4) + Orders (2) |
| 235-249 | Ask 5 | Fifth best sell | Quantity (8) + Price (4) + Orders (2) |

## Implementation Examples

### Complete WebSocket Client

#### JavaScript
=== "Javascript"
    ```javascript
    class MarketDataClient {
        constructor(appID, token) {
            this.wsUrl = `wss://ds.arrow.trade?appID=${appID}&token=${token}`;
            this.ws = null;
            this.subscribers = new Map();
        }
        
        connect() {
            return new Promise((resolve, reject) => {
                this.ws = new WebSocket(this.wsUrl);
                
                this.ws.onopen = () => {
                    console.log('WebSocket connected');
                    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('WebSocket disconnected');
                    this.reconnect();
                };
            });
        }
        
        subscribe(mode, tokens, callback) {
            const message = {
                code: 'sub',
                mode: mode,
                [mode]: tokens
            };
            
            this.ws.send(JSON.stringify(message));
            
            tokens.forEach(token => {
                this.subscribers.set(token, callback);
            });
        }
        
        handleMessage(data) {
            const buffer = new Uint8Array(data);
            const marketData = this.parseMarketData(buffer);
            
            const callback = this.subscribers.get(marketData.token);
            if (callback) {
                callback(marketData);
            }
        }
        
        parseMarketData(buffer) {
            const n = buffer.length;
            if (n === 13 || n === 29) {
                return this.parseLTP(buffer);
            } else if (n === 17 || n === 33) {
                return this.parseLTPC(buffer);
            } else if (n === 93 || n === 109) {
                return this.parseQuote(buffer);
            } else if (n === 249 || n === 265) {
                return this.parseFull(buffer);
            }
        }

        applyCAS(tick, data, baseLen) {
            if (data.length !== baseLen + 16) {
                return tick;
            }
            tick.imbalanceQty = this.bigEndianToSignedInt(data.slice(baseLen, baseLen + 8));
            tick.indicativeClose = this.bigEndianToInt(data.slice(baseLen + 8, baseLen + 12));
            tick.refPrice = this.bigEndianToInt(data.slice(baseLen + 12, baseLen + 16));
            return tick;
        }
        
        parseLTP(data) {
            const tick = {};
            tick.token = this.bigEndianToInt(data.slice(0, 4));
            tick.ltp = this.bigEndianToInt(data.slice(4, 8));
            return this.applyCAS(tick, data, 13);
        }

        parseLTPC(data) {
            const tick = {};
            tick.token = this.bigEndianToInt(data.slice(0, 4));
            tick.ltp = this.bigEndianToInt(data.slice(4, 8));
            tick.close = this.bigEndianToInt(data.slice(13, 17));
            tick.netChange = Math.round(
                (((tick.ltp - tick.close) / tick.close) * 100 + Number.EPSILON) * 100
            ) / 100 || 0;
            
            if (tick.ltp > tick.close) {
                tick.changeFlag = 43; // ascii code for +
            } else if (tick.ltp < tick.close) {
                tick.changeFlag = 45; // ascii code for -
            } else {
                tick.changeFlag = 32; // no change
            }
            
            return this.applyCAS(tick, data, 17);
        }
        
        parseQuote(data) {
            const tick = this.parseLTPC(data.slice(0, 17));
            
            tick.ltq = this.bigEndianToInt(data.slice(13, 17));
            tick.avgPrice = this.bigEndianToInt(data.slice(17, 21));
            tick.totalBuyQuantity = this.bigEndianToInt(data.slice(21, 29));
            tick.totalSellQuantity = this.bigEndianToInt(data.slice(29, 37));
            tick.open = this.bigEndianToInt(data.slice(37, 41));
            tick.high = this.bigEndianToInt(data.slice(41, 45));
            tick.close = this.bigEndianToInt(data.slice(45, 49));
            tick.low = this.bigEndianToInt(data.slice(49, 53));
            tick.volume = this.bigEndianToInt(data.slice(53, 61));
            tick.ltt = this.bigEndianToInt(data.slice(61, 65));
            tick.time = this.bigEndianToInt(data.slice(65, 69));
            tick.oi = this.bigEndianToInt(data.slice(69, 77));
            tick.oiDayHigh = this.bigEndianToInt(data.slice(77, 85));
            tick.oiDayLow = this.bigEndianToInt(data.slice(85, 93));
            
            // Recalculate net change with correct close price
            tick.netChange = Math.round(
                (((tick.ltp - tick.close) / tick.close) * 100 + Number.EPSILON) * 100
            ) / 100 || 0;
            
            if (tick.ltp > tick.close) {
                tick.changeFlag = 43; // ascii code for +
            } else if (tick.ltp < tick.close) {
                tick.changeFlag = 45; // ascii code for -
            } else {
                tick.changeFlag = 32; // no change
            }
            
            // parseLTPC on a 17-byte slice clears CAS; re-apply for quote+CAS
            delete tick.imbalanceQty;
            delete tick.indicativeClose;
            delete tick.refPrice;
            return this.applyCAS(tick, data, 93);
        }
        
        parseFull(data) {
            const tick = this.parseQuote(data.slice(0, 93));
            
            tick.lowerLimit = this.bigEndianToInt(data.slice(93, 97));
            tick.upperLimit = this.bigEndianToInt(data.slice(97, 101));
            
            const bids = [];
            const asks = [];
            
            const depthOffset = 109;

            for (let i = 0; i < 10; i++) {
                const offset = depthOffset + i * 14;
                const quantity = this.bigEndianToInt(data.slice(offset, offset + 8));
                const price = this.bigEndianToInt(data.slice(offset + 8, offset + 12));
                const orders = this.bigEndianToInt(data.slice(offset + 12, offset + 14));
                
                if (i >= 5) {
                    asks.push({ price, quantity, orders });
                } else {
                    bids.push({ price, quantity, orders });
                }
            }
            
            tick.bids = bids;
            tick.asks = asks;

            delete tick.imbalanceQty;
            delete tick.indicativeClose;
            delete tick.refPrice;
            return this.applyCAS(tick, data, 249);
        }
        
        bigEndianToInt(buffer) {
            let result = 0;
            for (let i = 0; i < buffer.length; i++) {
                result = (result << 8) | buffer[i];
            }
            return result;
        }

        // Signed two's-complement (needed for CAS imbalanceQty)
        bigEndianToSignedInt(buffer) {
            const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
            if (buffer.length === 8) {
                return Number(view.getBigInt64(0, false));
            }
            if (buffer.length === 4) {
                return view.getInt32(0, false);
            }
            return this.bigEndianToInt(buffer);
        }
    }

    // Usage
    const client = new MarketDataClient('your_app_id', 'your_token');
    await client.connect();

    client.subscribe('ltpc', [26009, 26000], (data) => {
        console.log(`${data.token}: LTP=${data.ltp}, Change=${data.netChange}%`);
    });
    ```

=== "Python"

    ```python
    import websocket
    import json
    import struct
    from threading import Thread

    class MarketDataClient:
        def __init__(self, app_id, token):
            self.ws_url = f"wss://ds.arrow.trade?appID={app_id}&token={token}"
            self.ws = None
            self.subscribers = {}
            
        def connect(self):
            websocket.enableTrace(True)
            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 subscribe(self, mode, tokens, callback):
            message = {
                'code': 'sub',
                'mode': mode,
                mode: tokens
            }
            
            self.ws.send(json.dumps(message))
            
            for token in tokens:
                self.subscribers[token] = callback
                
        def on_message(self, ws, message):
            market_data = self.parse_market_data(message)
            
            callback = self.subscribers.get(market_data['token'])
            if callback:
                callback(market_data)
                
        def parse_market_data(self, data):
            n = len(data)
            if n in (13, 29):
                return self.parse_ltp(data)
            elif n in (17, 33):
                return self.parse_ltpc(data)
            elif n in (93, 109):
                return self.parse_quote(data)
            elif n in (249, 265):
                return self.parse_full(data)

        def apply_cas(self, tick, data, base_len):
            if len(data) != base_len + 16:
                return tick
            tick['imbalance_qty'] = struct.unpack('>q', data[base_len:base_len+8])[0]
            tick['indicative_close'] = struct.unpack('>I', data[base_len+8:base_len+12])[0]
            tick['ref_price'] = struct.unpack('>I', data[base_len+12:base_len+16])[0]
            return tick

        def parse_ltp(self, data):
            tick = {
                'token': struct.unpack('>I', data[0:4])[0],
                'ltp': struct.unpack('>I', data[4:8])[0],
            }
            return self.apply_cas(tick, data, 13)
                
        def parse_ltpc(self, data):
            tick = {}
            tick['token'] = struct.unpack('>I', data[0:4])[0]
            tick['ltp'] = struct.unpack('>I', data[4:8])[0]
            tick['close'] = struct.unpack('>I', data[13:17])[0]
            
            if tick['close'] != 0:
                tick['net_change'] = round(
                    (((tick['ltp'] - tick['close']) / tick['close']) * 100), 2
                )
            else:
                tick['net_change'] = 0
                
            return self.apply_cas(tick, data, 17)
            
        def parse_quote(self, data):
            tick = self.parse_ltpc(data[:17])
            
            tick['ltq'] = struct.unpack('>I', data[13:17])[0]
            tick['avg_price'] = struct.unpack('>I', data[17:21])[0]
            tick['total_buy_quantity'] = struct.unpack('>Q', data[21:29])[0]
            tick['total_sell_quantity'] = struct.unpack('>Q', data[29:37])[0]
            tick['open'] = struct.unpack('>I', data[37:41])[0]
            tick['high'] = struct.unpack('>I', data[41:45])[0]
            tick['close'] = struct.unpack('>I', data[45:49])[0]
            tick['low'] = struct.unpack('>I', data[49:53])[0]
            tick['volume'] = struct.unpack('>Q', data[53:61])[0]
            tick['ltt'] = struct.unpack('>I', data[61:65])[0]
            tick['time'] = struct.unpack('>I', data[65:69])[0]
            tick['oi'] = struct.unpack('>Q', data[69:77])[0]
            tick['oi_day_high'] = struct.unpack('>Q', data[77:85])[0]
            tick['oi_day_low'] = struct.unpack('>Q', data[85:93])[0]
            
            # Recalculate net change with correct close price
            if tick['close'] != 0:
                tick['net_change'] = round(
                    (((tick['ltp'] - tick['close']) / tick['close']) * 100), 2
                )
            else:
                tick['net_change'] = 0

            tick.pop('imbalance_qty', None)
            tick.pop('indicative_close', None)
            tick.pop('ref_price', None)
            return self.apply_cas(tick, data, 93)
            
        def parse_full(self, data):
            tick = self.parse_quote(data[:93])
            
            tick['lower_limit'] = struct.unpack('>I', data[93:97])[0]
            tick['upper_limit'] = struct.unpack('>I', data[97:101])[0]
            
            bids = []
            asks = []
            
            depth_offset = 109

            for i in range(10):
                offset = depth_offset + i * 14
                quantity = struct.unpack('>Q', data[offset:offset+8])[0]
                price = struct.unpack('>I', data[offset+8:offset+12])[0]
                orders = struct.unpack('>H', data[offset+12:offset+14])[0]
                
                if i >= 5:
                    asks.append({'price': price, 'quantity': quantity, 'orders': orders})
                else:
                    bids.append({'price': price, 'quantity': quantity, 'orders': orders})
                    
            tick['bids'] = bids
            tick['asks'] = asks

            tick.pop('imbalance_qty', None)
            tick.pop('indicative_close', None)
            tick.pop('ref_price', None)
            return self.apply_cas(tick, data, 249)
        
    # Usage
    client = MarketDataClient('your_app_id', 'your_token')
    client.connect()

    def price_handler(data):
        print(f"{data['token']}: LTP={data['ltp']}, Change={data['net_change']}%")
        
    client.subscribe('ltpc', [26009, 26000], price_handler)
    ```

## Error Handling and Reconnection

### Connection Management
```javascript
class ReconnectingWebSocket {
    constructor(url) {
        this.url = url;
        this.reconnectInterval = 5000;
        this.maxReconnectAttempts = 10;
        this.reconnectAttempts = 0;
    }
    
    connect() {
        this.ws = new WebSocket(this.url);
        
        this.ws.onopen = () => {
            this.reconnectAttempts = 0;
            this.resubscribeAll();
        };
        
        this.ws.onclose = () => {
            if (this.reconnectAttempts < this.maxReconnectAttempts) {
                setTimeout(() => {
                    this.reconnectAttempts++;
                    this.connect();
                }, this.reconnectInterval);
            }
        };
    }
}
```

### Common Error Scenarios

| Error | Cause | Solution |
|-------|--------|----------|
| **Connection Failed** | Invalid credentials | Verify `appID` and `token` |
| **Authentication Error** | Expired token | Refresh authentication token |
| **Data Corruption** | Network issues | Implement data validation |
| **Rate Limiting** | Too many subscriptions | Throttle subscription requests |

## Performance Optimization

### Best Practices

**Subscription Management**
- **Batch Subscriptions**: Subscribe to multiple instruments in single requests
- **Mode Selection**: Use the minimal data mode required for your use case
- **Selective Unsubscription**: Remove unused subscriptions to reduce bandwidth
- **Connection Pooling**: Reuse connections across multiple data feeds

**Resource Management**
- **Memory Usage**: Parse and process data efficiently to prevent memory leaks  
- **CPU Optimization**: Use optimized binary parsers for high-frequency data
- **Network Bandwidth**: Monitor data throughput, especially with Full mode subscriptions
- **Buffer Management**: Implement proper buffer handling for binary data streams

## Market Data Applications

### Real-Time Portfolio Monitoring
```javascript
// Track P&L in real-time
function trackPortfolioValue(positions, marketData) {
    const currentValue = positions.reduce((total, position) => {
        const ltp = marketData[position.token]?.ltp || 0;
        const positionValue = position.quantity * ltp;
        return total + positionValue;
    }, 0);
    
    return currentValue;
}
```

### Order Book Analysis  
```javascript
// Calculate bid-ask spread and depth
function analyzeOrderBook(depth) {
    const bestBid = depth.bids[0];
    const bestAsk = depth.asks[0]; 
    
    return {
        spread: bestAsk.price - bestBid.price,
        spreadPercent: ((bestAsk.price - bestBid.price) / bestBid.price) * 100,
        bidDepth: depth.bids.reduce((sum, level) => sum + level.quantity, 0),
        askDepth: depth.asks.reduce((sum, level) => sum + level.quantity, 0)
    };
}
```

### Alert Systems
```javascript
// Price movement alerts
function createPriceAlert(token, threshold, callback) {
    return (marketData) => {
        if (marketData.token === token) {
            const changePercent = (marketData.netChange / marketData.previousClose) * 100;
            if (Math.abs(changePercent) >= threshold) {
                callback({
                    token: token,
                    change: changePercent,
                    ltp: marketData.ltp
                });
            }
        }
    };
}
```