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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions