Issue with Inconsistent Tick Data and Inaccurate Candle/Indicator Construction

Our trading bot, which subscribes to live tick data in “full” mode using your Market Data Feed V3, has been exhibiting some unexpected behavior:

  1. Inconsistent Tick Data:
  • We have observed that, for several instruments, only 2–3 candles are built within a 40-minute period, even during trending market conditions.
  • It appears that the API only sends tick data when there is a price change. As a result, when prices remain flat, no new ticks are transmitted. This behavior significantly delays the formation of new candles and may lead to inaccurate or incomplete indicator calculations.
  1. Volume Data Issues:
  • Although our feed includes a cumulative volume field (vtt), the incremental volume (calculated as the difference between successive ticks) often evaluates to zero.
  • This inconsistency affects our relative volume (RVOL) calculation and, consequently, the accuracy of our technical indicators (such as EMA and ATR), which are critical for our trading strategy.
  1. Discrepancies in Indicator Calculations:
  • Our system’s computed indicators, for example, the EMA values, sometimes differ substantially from those displayed on the broker’s platform. For instance, our calculations show an EMA value of 46.5 for an instrument while the platform indicates 33. This discrepancy could be a direct result of the delayed or missing tick data and the subsequent impact on candle construction.

If you are building candles through ltpc then it might be inaccurate but if you use OHLC data which has all the required values for candle generation and volume info then it might not be inaccurate.

i want to do the same thing but then how will trailing SL work ? my trailing SL requires tick by tick data for obvious reasons , what should i do ?

that is not true in case there is no price movement candles will not be formed even for OHLC data i have posted about it one week or two weeks back.

then what should i do ?? i want my indicators to work according to 3 min candles like we see on the chart , but i want my trailing SL to work as well

agreed , facing the same problem ig

Upstox team have to work and provide candles, you can do a work around if no candles beyond a cut off time you can assume that price doesn’t move and update OHLC as the previous price to generate candles and volume as 0.

tried that but still the ema is inaccurate , how can we get someone from upstox team to have a look at this issue

ema is usually for close price why it will be wrong if you provide correct close price.
I think they will reply on it, you have to wait for some time.

@Ketan , @Pradeep_Jaiswar can you please suggest something ?

FOR A TEMP. solution …can use GTT order (which has trailing sl functionality) iif u have fixed SL ,but its becomes quite messy if u are using ATR based SL, for that need to modiify order many times which is not not appropriate i think…

hwy ?? no updates ? what am i supposed to do

I am using the same setup, and I am getting the correct price and volume. My EMA calculations are also matching the correct values as shown on the chart.

I recommend that you ensure you’re building your candles using the OHLCV data received from the WebSocket response, rather than relying on the Last Traded Price (LTP) and volume alone. This should help address the discrepancies you’re seeing in your indicators.

could you further elaborate please ?

You can save ohlc data to csv every minute. And then generate indicators using that csv.

Hello,

Here is a below code to cache stock data and update it by WebSocket:

private void CacheStockData()
{
    // Initialize a hash set to store unique instrument keys
    HashSet<string> InstrumentKeys = new HashSet<string> { };

    // Create instances of APIs for fetching historical and market data
    HistoryApi api = new HistoryApi();
    MarketQuoteApi quoteApi = new MarketQuoteApi();

    // Initialize the stock cache dictionary
    Program.StockCache = new Dictionary<string, StockCache>();

    // Iterate through all available scripts
    foreach (var scrip in GetScripts())
    {
        // Generate a unique instrument key for the script
        string instrumentKey = Program.GetInstrumentKey(scrip);

        // Ensure the stock cache entry exists for this instrument key
        if (!Program.StockCache.ContainsKey(instrumentKey))
        {
            Program.StockCache[instrumentKey] = new StockCache();
        }

        // Fetch 1-minute historical data for the instrument
        var res = api.GetHistoricalCandleData(instrumentKey, "1minute", DateTime.Now.ToString("yyyy-MM-dd"));
        if (res != null && res.Status == UpStoxClient.Model.GetHistoricalCandleResponse.StatusEnum.Success && res.Data != null)
        {
            // Process and store 1-minute data excluding candles after 3:15 PM
            Program.StockCache[instrumentKey].OneMinuteData.AddRange(
                ProcessToQuote(res.Data.Candles.Where(p => ((DateTime)p[0]).TimeOfDay < new TimeSpan(15, 15, 00)).ToList())
            );
        }

        // Fetch 1-day historical data for the instrument
        res = api.GetHistoricalCandleData(instrumentKey, "day", DateTime.Now.ToString("yyyy-MM-dd"));
        if (res != null && res.Status == UpStoxClient.Model.GetHistoricalCandleResponse.StatusEnum.Success && res.Data != null)
        {
            // Process and store 1-day data
            Program.StockCache[instrumentKey].OneDayData.AddRange(ProcessToQuote(res.Data.Candles));
        }

        // Check if today's 1-minute data is already present
        if (!Program.StockCache[instrumentKey].OneMinuteData.Any(p => p.Date.Date == DateTime.Now.Date))
        {
            // Fetch today's intraday 1-minute data if not present
            var intradayRes = api.GetIntraDayCandleData(instrumentKey, "1minute");
            if (intradayRes != null && intradayRes.Status == UpStoxClient.Model.GetIntraDayCandleResponse.StatusEnum.Success && intradayRes.Data != null)
            {
                Program.StockCache[instrumentKey].OneMinuteData.AddRange(ProcessToQuote(intradayRes.Data.Candles));
            }
        }

        // Check if today's 1-day data is already present
        if (!Program.StockCache[instrumentKey].OneDayData.Any(p => p.Date.Date == DateTime.Now.Date))
        {
            // Fetch the latest market quote data if today's 1-day data is missing
            var latestiQuote = quoteApi.GetFullMarketQuote(instrumentKey: instrumentKey);
            if (latestiQuote != null && latestiQuote.Status == UpStoxClient.Model.GetFullMarketQuoteResponse.StatusEnum.Success && latestiQuote.Data.Count > 0)
            {
                var obj = latestiQuote.Data.FirstOrDefault().Value;
                var iQuote = new iQuote(
                    DateTimeOffset.FromUnixTimeMilliseconds(long.Parse(obj.LastTradeTime)).LocalDateTime.Date,
                    (decimal)obj.Ohlc.Open, (decimal)obj.Ohlc.High, (decimal)obj.Ohlc.Low, (decimal)obj.Ohlc.Close, Convert.ToDecimal(obj.Volume)
                );

                // Add the new 1-day data if it doesn't already exist
                if (!Program.StockCache[instrumentKey].OneDayData.Select(p => p.Date).Any(p => p.Date == iQuote.Date.Date))
                {
                    Program.StockCache[instrumentKey].OneDayData.Add(iQuote);
                }
            }
        }

        // Add the instrument key to the set of keys being tracked
        InstrumentKeys.Add(instrumentKey);

        // Pause briefly to avoid overwhelming the API
        Thread.Sleep(100);
    }

    // Initialize a market data streamer to monitor updates for all instrument keys
    Streamer = new MarketDataStreamer(InstrumentKeys, Mode.Full);

    // Subscribe to the market update event
    Streamer.OnMarketUpdate += Streamer_OnMarketUpdate;

    // Connect the streamer to start receiving updates
    Streamer.Connect();
}

private void Streamer_OnMarketUpdate(MarketUpdate obj)
{
    if (obj != null && obj.Feeds != null && obj.Feeds.Count > 0)
    {
        foreach (var feed in obj.Feeds)
        {
            var instrumentKey = feed.Key;

            // Ensure the stock cache entry exists for this instrument key
            if (!Program.StockCache.ContainsKey(instrumentKey))
            {
                Program.StockCache[instrumentKey] = new StockCache();
            }

            // Extract 1-day candle data and update the stock cache
            var dayCandle = feed.Value.Ff.MarketFF.MarketOHLC.Ohlc.First(p => p.Interval == "1d");
            iQuote dayQuote = new iQuote()
            {
                Date = DateTimeOffset.FromUnixTimeMilliseconds(dayCandle.Ts).LocalDateTime,
                Open = (decimal)dayCandle.Open,
                High = (decimal)dayCandle.High,
                Low = (decimal)dayCandle.Low,
                Close = (decimal)dayCandle.Close,
                Volume = dayCandle.Volume
            };

            // Update or insert the 1-day data entry
            var stockCacheEntry = Program.StockCache[instrumentKey];
            var index = stockCacheEntry.OneDayData.FindIndex(p => p.Date == dayQuote.Date);
            if (index >= 0)
            {
                stockCacheEntry.OneDayData[index] = dayQuote;
            }

            // Extract 1-minute candle data and update the stock cache
            var minuteCandle = feed.Value.Ff.MarketFF.MarketOHLC.Ohlc.First(p => p.Interval == "I1");
            iQuote minuteQuote = new iQuote()
            {
                Date = DateTimeOffset.FromUnixTimeMilliseconds(minuteCandle.Ts).LocalDateTime,
                Open = (decimal)minuteCandle.Open,
                High = (decimal)minuteCandle.High,
                Low = (decimal)minuteCandle.Low,
                Close = (decimal)minuteCandle.Close,
                Volume = minuteCandle.Volume
            };

            // Update or insert the 1-minute data entry
            index = stockCacheEntry.OneMinuteData.FindIndex(p => p.Date == minuteQuote.Date);
            if (index >= 0)
            {
                stockCacheEntry.OneMinuteData[index] = minuteQuote; // Update existing entry
            }
            else
            {
                stockCacheEntry.OneMinuteData.Add(minuteQuote); // Add new entry
            }
        }
    }
}

There are several libraries available like Skender C# to calculate indicators by passing the data.

Thanks & Regards