Now Live: Live Market Feed on DhanHQ Trading APIs

Hi @Hardik

is there have any way to check how many rate limit I am right now using…

it’s help’s us to control and use…

I know there have Rate Limit Table, but I want to check how many rate limit I am using every per day and every per minute

Hello @ashokprajapati

This might not be feasible at our end to provide, as this is maintained on another layer and polling this separately will not be possible. You can keep this counter at your end itself, given the script you are running must have logic for when APIs are polled.

@Hardik
I am trying with C# and with the below code on Wednesday 10:16 PM, I am able to connect and subscribe for the feed but not receiving any data. I have verified that the connection gets closed if I pass wrong client id or token. When it’s connected, not receiving any data over websocket. I subscribed for the GOLDM future contracts.
The socket remains connected, but no data comes over it. Can you please check what is missing or wrong?

using System;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Collections.Generic;
using OptionChain.DataRetriever;

public class DhanHQWebSocketClient
{
    private ClientWebSocket _webSocket;
    private readonly string _websocketUrl;
    private Thread _receiveThread;
    private Thread _pingThread;

    public DhanHQWebSocketClient(string token, string clientId)
    {
        _websocketUrl = $"wss://api-feed.dhan.co?version=2&token={token}&clientId={clientId}&authType=2";
        _webSocket = new ClientWebSocket();
        
    }

    public void Connect()
    {
        _webSocket.ConnectAsync(new Uri(_websocketUrl), CancellationToken.None).Wait();
        Console.WriteLine("WebSocket connection established");
    }

    public void SubscribeInstruments(List<Instrument> instruments)
    {
        for (int i = 0; i < instruments.Count; i += 100)
        {
            var batch = instruments.GetRange(i, Math.Min(100, instruments.Count - i));
            var subscriptionMessage = new
            {
                RequestCode = 15,
                InstrumentCount = batch.Count,
                InstrumentList = batch
            };

            var jsonMessage = JsonSerializer.Serialize(subscriptionMessage);
            var buffer = Encoding.UTF8.GetBytes(jsonMessage);
            _webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None).Wait();
            Console.WriteLine($"Subscribed to batch {i / 100 + 1}");
        }
    }

    public void StartReceiving()
    {
        _receiveThread = new Thread(() =>
        {
            var buffer = new byte[1024 * 4];
            while (_webSocket.State == WebSocketState.Open)
            {
                var result = _webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None).Result;

                if (result.MessageType == WebSocketMessageType.Binary)
                {
                    ProcessBinaryMessage(buffer, result.Count);
                }
                else if (result.MessageType == WebSocketMessageType.Text)
                {
                    Console.WriteLine("Received: " + System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count));
                }
                else if (result.MessageType == WebSocketMessageType.Close)
                {
                    Console.WriteLine("Received close message");
                }
            }
            Console.WriteLine("Websocket is NOT open");
        });
        _receiveThread.Start();
    }

    private void ProcessBinaryMessage(byte[] buffer, int count)
    {
        if (count < 8) return;

        int responseCode = buffer[0];
        int exchangeSegment = buffer[3];
        int securityId = BitConverter.ToInt32(buffer, 4);

        switch (responseCode)
        {
            case 1: // Index Packet
                ProcessIndexPacket(buffer, securityId);
                break;
            case 2: // Ticker Packet
                ProcessTickerPacket(buffer, securityId);
                break;
            case 4: // Quote Packet
                ProcessQuotePacket(buffer, securityId);
                break;
            case 5: // Open Interest Packet
                ProcessOIPacket(buffer, securityId);
                break;
            case 6: // Previous Close Packet
                ProcessPrevClosePacket(buffer, securityId);
                break;
            case 7: // Market Status Packet
                ProcessMarketStatusPacket(buffer);
                break;
            case 8: // Full Packet
                ProcessFullPacket(buffer, securityId);
                break;
            case 50: // Feed Disconnect
                ProcessFeedDisconnectPacket(buffer);
                break;
            default:
                Console.WriteLine($"Unknown packet type: {responseCode}");
                break;
        }
    }

    private void ProcessIndexPacket(byte[] buffer, int securityId)
    {
        float indexValue = BitConverter.ToSingle(buffer, 8);
        Console.WriteLine($"Index Packet: SecurityId={securityId}, IndexValue={indexValue}");
    }

    private void ProcessTickerPacket(byte[] buffer, int securityId)
    {
        float lastTradedPrice = BitConverter.ToSingle(buffer, 8);
        int lastTradeTime = BitConverter.ToInt32(buffer, 12);
        Console.WriteLine($"Ticker: SecurityId={securityId}, LTP={lastTradedPrice}, LTT={lastTradeTime}");
    }

    private void ProcessQuotePacket(byte[] buffer, int securityId)
    {
        float lastTradedPrice = BitConverter.ToSingle(buffer, 8);
        short lastTradedQuantity = BitConverter.ToInt16(buffer, 12);
        int lastTradeTime = BitConverter.ToInt32(buffer, 13);
        float averageTradePrice = BitConverter.ToSingle(buffer, 19);
        int volume = BitConverter.ToInt32(buffer, 23);
        int totalSellQuantity = BitConverter.ToInt32(buffer, 27);
        int totalBuyQuantity = BitConverter.ToInt32(buffer, 31);

        Console.WriteLine($"Quote: SecurityId={securityId}, " +
            $"LTP={lastTradedPrice}, " +
            $"LTQ={lastTradedQuantity}, " +
            $"LTT={lastTradeTime}, " +
            $"ATP={averageTradePrice}, " +
            $"Volume={volume}, " +
            $"TotalSellQty={totalSellQuantity}, " +
            $"TotalBuyQty={totalBuyQuantity}");
    }

    private void ProcessOIPacket(byte[] buffer, int securityId)
    {
        int openInterest = BitConverter.ToInt32(buffer, 8);
        Console.WriteLine($"Open Interest: SecurityId={securityId}, OI={openInterest}");
    }

    private void ProcessPrevClosePacket(byte[] buffer, int securityId)
    {
        int prevClosePrice = BitConverter.ToInt32(buffer, 8);
        int prevOpenInterest = BitConverter.ToInt32(buffer, 12);
        Console.WriteLine($"Previous Close: SecurityId={securityId}, PrevClose={prevClosePrice}, PrevOI={prevOpenInterest}");
    }

    private void ProcessMarketStatusPacket(byte[] buffer)
    {
        // Market status packet processing
        Console.WriteLine("Market Status Packet Received");
    }

    private void ProcessFullPacket(byte[] buffer, int securityId)
    {
        float lastTradedPrice = BitConverter.ToSingle(buffer, 8);
        short lastTradedQuantity = BitConverter.ToInt16(buffer, 12);
        int lastTradeTime = BitConverter.ToInt32(buffer, 14);
        float averageTradePrice = BitConverter.ToSingle(buffer, 19);
        int volume = BitConverter.ToInt32(buffer, 23);
        int totalSellQuantity = BitConverter.ToInt32(buffer, 27);
        int totalBuyQuantity = BitConverter.ToInt32(buffer, 31);
        int openInterest = BitConverter.ToInt32(buffer, 35);

        Console.WriteLine($"Full Packet: SecurityId={securityId}, " +
            $"LTP={lastTradedPrice}, " +
            $"LTQ={lastTradedQuantity}, " +
            $"LTT={lastTradeTime}, " +
            $"ATP={averageTradePrice}, " +
            $"Volume={volume}, " +
            $"TotalSellQty={totalSellQuantity}, " +
            $"TotalBuyQty={totalBuyQuantity}, " +
            $"OI={openInterest}");

        // Market Depth parsing (last 100 bytes)
        ParseMarketDepth(buffer, 63);
    }

    private void ParseMarketDepth(byte[] buffer, int startIndex)
    {
        for (int i = 0; i < 5; i++)
        {
            int bidQuantity = BitConverter.ToInt32(buffer, startIndex + i * 20);
            int askQuantity = BitConverter.ToInt32(buffer, startIndex + i * 20 + 4);
            short numBidOrders = BitConverter.ToInt16(buffer, startIndex + i * 20 + 8);
            short numAskOrders = BitConverter.ToInt16(buffer, startIndex + i * 20 + 10);
            float bidPrice = BitConverter.ToSingle(buffer, startIndex + i * 20 + 12);
            float askPrice = BitConverter.ToSingle(buffer, startIndex + i * 20 + 16);

            Console.WriteLine($"Market Depth Level {i + 1}: " +
                $"BidQty={bidQuantity}, " +
                $"AskQty={askQuantity}, " +
                $"NumBidOrders={numBidOrders}, " +
                $"NumAskOrders={numAskOrders}, " +
                $"BidPrice={bidPrice}, " +
                $"AskPrice={askPrice}");
        }
    }

    private void ProcessFeedDisconnectPacket(byte[] buffer)
    {
        short disconnectionCode = BitConverter.ToInt16(buffer, 8);
        Console.WriteLine($"Feed Disconnected. Reason Code: {disconnectionCode}");
    }

    public void StartPinging()
    {
        _pingThread = new Thread(() =>
        {
            while (_webSocket.State == WebSocketState.Open)
            {
                Thread.Sleep(9000);
                try
                {
                    _webSocket.SendAsync(
                        new ArraySegment<byte>(new byte[0]),
                        WebSocketMessageType.Text,
                        true,
                        CancellationToken.None
                    ).Wait();
                    Console.WriteLine("Sent Ping");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Ping error: {ex.Message}");
                    break;
                }
            }
            Console.WriteLine("Websocket state is: " + _webSocket.State);
        });
        _pingThread.Start();
    }

    public void Run(List<Instrument> instruments)
    {
        Connect();
        SubscribeInstruments(instruments);
        StartReceiving();
        StartPinging();
    }
}

public class Instrument
{
    public string ExchangeSegment { get; set; }
    public string SecurityId { get; set; }
}

class DhanDataRetriever
{
    static void Main(string[] args)
    {
        string token = CredentialManager.GetStoredPassword("Dhan", "DataApiToken");
        string clientId = CredentialManager.GetStoredPassword("Dhan", "ClientId");

        var instruments = new List<Instrument>
        {
            new Instrument { ExchangeSegment = "MCX", SecurityId = "440706" },
            new Instrument { ExchangeSegment = "MCX", SecurityId = "439223" }
        };

        var client = new DhanHQWebSocketClient(token, clientId);
        client.Run(instruments);

        Console.ReadLine();
    }
}

Hello @Ashutosh1

Welcome to MadeForTrade community!

Looks like the exchange segment being passed is incorrect. For MCX, it needs to be MCX_COMM

Thanks @Hardik for pointing it out. Sometimes feels like banging my head against the wall for making such silly mistake!

1 Like

Happens with the best of us!

Keep on trying, for any queries we are here don’t worry.

Hi @Hardik I have been exploring the real time feed for last few days and I am noticing that sometimes randomly for few scripts the price that is received in the ticker packet is wrong and way off the range limit. I have the logs for each tick price, I will try to match this with what is published by NSE and post more details in few days.

But this price coming totally out of range, are you aware of this issue?

e.g.:

  1. today for axis bank
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:30
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:35
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:35
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:37
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:37
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:38
    Ticker: SecurityId=AXISBANK, LTP=1163, LTT=09:41:38
    Ticker: SecurityId=AXISBANK, LTP=1163.2, LTT=09:41:40
    Ticker: SecurityId=AXISBANK, LTP=1163.2, LTT=09:41:40
    Ticker: SecurityId=AXISBANK, LTP=6749, LTT=09:41:55
    Ticker: SecurityId=AXISBANK, LTP=1162.85, LTT=09:41:59
    Ticker: SecurityId=AXISBANK, LTP=1162.85, LTT=09:41:59
    Ticker: SecurityId=AXISBANK, LTP=1162.85, LTT=09:42:02
    Ticker: SecurityId=AXISBANK, LTP=1162.85, LTT=09:42:02
    Ticker: SecurityId=AXISBANK, LTP=1162.9, LTT=09:42:09
    Ticker: SecurityId=AXISBANK, LTP=1162.9, LTT=09:42:09
    Ticker: SecurityId=AXISBANK, LTP=1162.9, LTT=09:42:12

  2. yesterday too (4th dec) for Axis bank at 04-12-2024 13:16:00, got price of 7236.3.

  3. 04-12-2024 12:08:31, for HDFC bank, the price was 295.5

  4. 04-12-2024 12:46:44 for INfosys, the price was 418.2

  5. 04-12-2024 11:02:46 for tatamotors, the price was 145.36

I have seen it for other scripts too, 2 days back for tata steel.

Seems like we need to build logic to validate if the price is in the proper range. now I am really doubting the rest of the data. Really need to match it with NSE :frowning:

@Hardik were you able to check why these out of range price comes once in a while?

Hello @Ashutosh1

Not yet. And this shouldn’t be the case as well since the same data is reflected on all Dhan platforms as well. Over here, I can see you have reverse mapped Security ID to the Ticker Symbol. Can you check once at your end if there is any mapping error happening here?

Rest assured on data accuracy as thousands of other users are using these same websockets and never seen this instance.

@Hardik thanks for responding back.
I understated such sporadic issues are difficult for developers to accept which they can’t reproduce. But this doesn’t reduce the authenticity and genuineness of those issues. The industry where I work, such issues can be life threatening :slight_smile: We never say to any customer that no one else reported such issue and hence you are wrong.

I am also in Software industry for 20 years and I still code a lot (really a lot) and have been coding in assembly,C++,C# etc. I would not have reported if my code would have such silly issues :slight_smile:

So, I urge you to please have it checked with your team. Ask them to subscribe to all stocks of Nifty50, just dump it and then match it at the end. Run it for a week or so as it may not happen always.

1 Like

@Ashutosh1 Thank you for sharing your feedback and for taking the time.

Rest assured, we do not take any customer-reported issues lightly, regardless of their frequency or whether they are commonly encountered by other users. Your concerns are valid, and we are committed to investigating them thoroughly. We understand how challenging sporadic issues can be to diagnose and address, especially when they cannot be consistently reproduced. But that is not a reason for us to not work on your point.

We will try to proceed with running the simulation you proposed and will keep you updated on our findings.

In the meantime, if you have additional details, logs, or patterns that could help us better understand the issue, we encourage you to share them.

@Hardik found one more issue. When we subscribe for price of multiple stocks in a single batch, then then all the previous close packets/responses contains data for the last stock in the subscription list.

E.g.: I subscribed for following stocks: APOLLOHOSP,AXISBANK,BAJAJFINSV,BAJFINANCE,BHARTIARTL,BPCL,BRITANNIA,CIPLA,COALINDIA,GRASIM,HDFCBANK,HINDUNILVR,ICICIBANK,INFY,ITC,JSWSTEEL,MARUTI,NESTLEIND,NTPC,ONGC,RELIANCE,SBILIFE,SHRIRAMFIN,TATACONSUM,TATAMOTORS,TATASTEEL,TECHM,WIPRO

I get previous close packets multiple times but all of them contains data for the last stock i.e. WIPRO in this case.

I hope your developers can reproduce this issue easily.

@Hardik also, the documentation at Live Market Feed - DhanHQ Ver 2.0 / API Document mentions int32 for Previous day closing price. It should be float32.