Skip to content

Real-time data stops updating when switching back to 1-minute timeframe #152

@walrusix

Description

@walrusix

Description:
I am using the TradingView Chart Library in my project.

The historical candle feed is generated randomly on the backend so the chart is not empty.

I also generate a random real-time data feed and send it to the chart via WebSocket.

Problem:

When I am on the 1-minute timeframe, the real-time feed works correctly and shows active price updates.

Switching to the 5-minute timeframe, the real-time price is still visible and updating as expected.

However, if I switch back to the 1-minute timeframe, the real-time data stops updating on the chart.

By inspecting the WebSocket data stream, I confirmed that real-time price updates are still being sent continuously. Despite this, the 1-minute chart does not reflect any real-time movement.

Code:
(Provide your relevant code here)

Environment:

TradingView Charting Library

WebSocket real-time feed

Randomized backend-generated historical candles

Expected Behavior:
Real-time updates should continue to display correctly when switching between timeframes, including back to the 1-minute chart.

Additional Notes:
This seems related to how subscribeBars and resolution changes are handled.

`

<title>TradingView Fixed Exchange & |adj</title> <script type="text/javascript" src="charting_library/charting_library.standalone.js"></script> <script type="text/javascript" src="datafeeds/udf/dist/bundle.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> <script> async function feedSignRequest(path, body = "", feedTimestamp = Math.floor(Date.now() / 1000)) { try { const feedSignature = await window.chrome.webview.hostObjects.hmacHelper.Sign(path, body, feedTimestamp); return { feedSignature, feedTimestamp }; } catch (err) { console.error("HMAC Sign Error:", err); return { feedSignature: "", feedTimestamp }; } }
    const originalFetch = window.fetch;
    window.fetch = async function (input, init = {}) {
        const url = typeof input === "string" ? input : input.url;
        const path = new URL(url, window.location.origin).pathname;
        const body = init.body || "";
        const { feedSignature, feedTimestamp } = await feedSignRequest(path, body);

        init.headers = {
            ...(init.headers || {}),
            "X-FeedSignature": feedSignature,
            "X-FeedTimestamp": feedTimestamp,
        };

        return originalFetch(input, init);
    };
</script>

<script>
    async function wsSignRequest(path, body = "", wsTimestamp = Math.floor(Date.now() / 1000)) {
        try {
            const wsSignature = await window.chrome.webview.hostObjects.hmacHelper.Sign(path, body, wsTimestamp);
            return { wsSignature, wsTimestamp };
        } catch (err) {
            console.error("HMAC Sign Error:", err);
            return { wsSignature: "", wsTimestamp };
        }
    }

    const originalFetch = window.fetch;
    window.fetch = async function (input, init = {}) {
        const url = typeof input === "string" ? input : input.url;
        const path = new URL(url, window.location.origin).pathname;
        const body = init.body || "";
        const { wsSignature, wsTimestamp } = await wsSignRequest(path, body);

        init.headers = {
            ...(init.headers || {}),
            "X-WsSignature": wsSignature,
            "X-WsTimestamp": wsTimestamp,
        };

        return originalFetch(input, init);
    };
</script>

<style>
    html, body, #tv_chart_container {
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100vh;
    }
</style>
<script>
    function initChart() {
        let currentExchange = 'DemoExchange';
        let currentAdjustment = 'normal';
        let myDropdownApiL = null;
        let liveWs = null;
        let subscriberUID = null;
        let lastBarCache = {};

        function normalizeSymbol(symbolName) {
            let parts = symbolName.split(':').filter(Boolean);
            if (parts.length > 2) symbolName = parts[0] + ':' + parts[parts.length - 1];
            else if (parts.length === 2) symbolName = parts.join(':');
            return symbolName.replace(/\|(1|adj)+$/, '');
        }

        function buildSymbol(s) {
            let symbol = normalizeSymbol(s.symbol);
            let exchange = currentExchange;

            if (s.exchange) currentExchange = s.exchange;
            else if (s.symbol.includes(':')) currentExchange = s.symbol.split(':')[0];

            if (!symbol.includes(':') && exchange) symbol = `${exchange}:${symbol}`;

            if (currentAdjustment === 'adj' && !symbol.endsWith('|adj')) symbol += '|adj';
            else if (currentAdjustment === 'normal') symbol = symbol.replace(/\|adj$/, '');

            return symbol;
        }

        function getBarTime(timestamp, resolution) {
            const date = new Date(timestamp * 1000);

            if (resolution.includes('D')) {
                date.setHours(0, 0, 0, 0);
                return Math.floor(date.getTime() / 1000);
            } else if (resolution.includes('W')) {
                const day = date.getDay();
                date.setDate(date.getDate() - day);
                date.setHours(0, 0, 0, 0);
                return Math.floor(date.getTime() / 1000);
            } else {
                const minutes = parseInt(resolution) || 1;
                const minutesSinceEpoch = Math.floor(timestamp / 60);
                const barMinute = Math.floor(minutesSinceEpoch / minutes) * minutes;
                return barMinute * 60;
            }
        }

        const baseDatafeed = new Datafeeds.UDFCompatibleDatafeed("https://localhost:5001/api/trading-view/udf");

        const customDatafeed = {
            onReady: (callback) => baseDatafeed.onReady(callback),
            searchSymbols: (userInput, exchange, symbolType, onResult) =>
                baseDatafeed.searchSymbols(userInput, exchange, symbolType, onResult),
            resolveSymbol: (symbolName, onResolve, onError) =>
                baseDatafeed.resolveSymbol(symbolName, onResolve, onError),

            getBars: (symbolInfo, resolution, periodParams, onResult, onError) => {
                baseDatafeed.getBars(symbolInfo, resolution, periodParams, (bars, meta) => {
                    if (bars && bars.length > 0) {
                        const lastBar = bars[bars.length - 1];
                        const cacheKey = `${symbolInfo.name}_${resolution}`;
                        lastBarCache[cacheKey] = { ...lastBar };
                        console.log('📊 Last bar cached:', lastBar);
                    }
                    onResult(bars, meta);
                }, onError);
            },

            subscribeBars: (symbolInfo, resolution, onTick, listenerGuid, onResetCacheNeededCallback) => {
                console.log('=== subscribeBars called ===');
                subscriberUID = listenerGuid;

                window.tvOnTick = onTick;
                window.currentSymbol = symbolInfo.name;
                window.currentResolution = resolution;

                if (liveWs && liveWs.readyState === WebSocket.OPEN) {
                    liveWs.send(JSON.stringify({
                        type: "subscribe",
                        symbol: symbolInfo.name,
                        resolution: resolution
                    }));
                }
            },

            unsubscribeBars: (listenerGuid) => {
                console.log('=== unsubscribeBars called ===');
                subscriberUID = null;
                window.tvOnTick = null;
                window.currentSymbol = null;
                window.currentResolution = null;

                if (liveWs && liveWs.readyState === WebSocket.OPEN) {
                    liveWs.send(JSON.stringify({
                        type: "unsubscribe"
                    }));
                }
            }
        };


    async function connectWebSocket() {
        try {
            const path = "/ws";
            const wsTimestamp = Math.floor(Date.now() / 1000);


            const wsSignature = await window.chrome.webview.hostObjects.hmacHelper.Sign(
                path,
                "", 
                wsTimestamp
            );
            const activeSymbol = widget.activeChart().symbol();

            const wsUrl = `ws://localhost:5090/ws?timestamp=${wsTimestamp}&signature=${encodeURIComponent(wsSignature)}&symbol=${activeSymbol}`;

            console.log("🔗 Connecting to WebSocket with signature...");

            liveWs = new WebSocket(wsUrl);

            liveWs.onopen = () => {
                console.log("✅ Connected to WebSocket server for live data!");
            };

            liveWs.onmessage = (event) => {
                try {
                    const data = JSON.parse(event.data);
                    console.log("📊 Received data:", data);

                    if (!window.tvOnTick || typeof window.tvOnTick !== 'function') {
                        console.warn("⚠️ tvOnTick callback not found!");
                        return;
                    }

                    const resolution = window.currentResolution || '1';
                    const symbol = window.currentSymbol;
                    const cacheKey = `${symbol}_${resolution}`;

                    const barTime = getBarTime(data.t, resolution);

                    let lastBar = lastBarCache[cacheKey];
                     console.log("-------------------------------------");
                    if (!lastBar || lastBar.time < barTime * 1000) {
                        const newBar = {
                            time: barTime * 1000,
                            open: data.o,
                            high: data.h,
                            low: data.l,
                            close: data.c,
                            volume: data.v
                        };
                        lastBarCache[cacheKey] = { ...newBar };
                        console.log("🆕 **********************************:", newBar);
                        alert("******************");
                        window.tvOnTick(newBar);
                    } else {

                        lastBar.high = Math.max(lastBar.high, data.h);
                        lastBar.low = Math.min(lastBar.low, data.l);
                        lastBar.close = data.c;
                        lastBar.volume = data.v;
                        console.log("🔄 Updating current bar:", lastBar);
                        window.tvOnTick({ ...lastBar });
                    }
                } catch (err) {
                    console.error("❌ Invalid WebSocket message:", err);
                }
            };

            liveWs.onclose = () => {
                console.log("❌ WebSocket disconnected - Reconnecting in 3s...");
                setTimeout(connectWebSocket, 3000);
            };

            liveWs.onerror = (err) => {
                console.error("❌ WebSocket error:", err);
            };

        } catch (err) {
            console.error("❌ Error creating WebSocket signature:", err);
            setTimeout(connectWebSocket, 3000);
        }
    }

        connectWebSocket();

        const widget = new TradingView.widget({
            symbol: 'DemoExchange:AAPL',
            interval: '1',
            container: "tv_chart_container",
            datafeed: customDatafeed,
            library_path: "charting_library/",
            autosize: true,
            locale: "fa",
            theme: "light",
            enabled_features: ["study_templates", "tick_resolution", "show_exchange_logos","items_favoriting","show_symbol_logos"  ],
            disabled_features: ["use_localstorage_for_settings"]
        });

        let chart;
        widget.onChartReady(() => {
            chart = widget.activeChart();

            chart.onSymbolChanged().subscribe(null, function(newSymbol) {
                if (!newSymbol || !newSymbol.name) return;

                if (newSymbol.exchange) currentExchange = newSymbol.exchange;
                else if (newSymbol.name.includes(':')) currentExchange = newSymbol.name.split(':')[0];

                let symbolToSend = newSymbol.name.includes(':') ? newSymbol.name : `${currentExchange}:${newSymbol.name}`;


                console.log("🔄 Symbol Changed to:", symbolToSend);

                lastBarCache = {};

                fetch("https://localhost:5001/api/ChartEvents/SymbolChanged", {
                    method: "POST",
                    headers: { "Content-Type": "application/json" },
                    body: JSON.stringify({
                        symbol_name: symbolToSend,
                        full_name: newSymbol.full_name,
                        exchange: currentExchange,

                    })
                })
                .then(res => res.json())
                .then(data => console.log("✅ Server Response:", data))
                .catch(err => console.error("❌ API Error:", err));

                if (liveWs && liveWs.readyState === WebSocket.OPEN) {
                    liveWs.send(JSON.stringify({
                        type: "symbol_changed",
                        symbol_name: symbolToSend
                    }));
                }
            });
        });
    }

    window.addEventListener("DOMContentLoaded", initChart);
</script>
`

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions