I've created a DataFeed
class that establishes a WebSocket connection to stream real-time data for use in a TradingView chart. Here's the code for the class:
export default class DataFeed { constructor(symbol) { this.symbol = symbol; this.socket = new WebSocket("ws://socket"); this.subscribers = {}; this.pingInterval = null; this.socket.onopen = () => { console.log("WebSocket connection established"); this.subscribe(this.symbol, "1m"); // Default to '1m' interval this.startPing(); // Start the ping interval }; this.socket.onmessage = (event) => this.onMessage(event); this.socket.onerror = (error) => console.error("WebSocket Error:", error); this.socket.onclose = () => console.log("WebSocket closed"); } onMessage(event) { const message = event?.data; if (message.includes("welcome") || message.includes("Welcome")) { return; } try { const receivedData = JSON.parse(message); this.processData(receivedData); } catch (error) { console.error("Failed to parse WebSocket message:", message, error); } } subscribe(symbol, resolution) { const subscribeMessage = JSON.stringify({ action: "subscribe", channel: `kline_${resolution}`, symbol: [`${symbol}_USDT`], }); this.socket.send(subscribeMessage); } startPing() { if (this.pingInterval) { clearInterval(this.pingInterval); } this.pingInterval = setInterval(() => { if (this.socket.readyState === WebSocket.OPEN) { this.socket.send( JSON.stringify({ action: "ping", symbol: [], channel: "heartbeat", }) ); } }, 5000); } processData(message) { const { data } = message; if (!data || !data.startTime || data === "pong") { return; } const bar = { time: new Date(data.startTime).getTime(), close: parseFloat(data.closePrice), open: parseFloat(data.openPrice), high: parseFloat(data.highPrice), low: parseFloat(data.lowPrice), volume: parseFloat(data.volume), }; console.log("Processed bar data:", bar?.close, bar?.volume); // Notify all subscribers about the new bar Object.values(this.subscribers).forEach((sub) => sub.forEach((s) => s.callback(bar)) ); } onReady(callback) { callback({ supported_resolutions: ["1", "5", "15", "30", "60", "D", "W", "M"], supports_marks: false, supports_timescale_marks: false, supports_time: true, }); } resolveSymbol(symbolName, onSymbolResolvedCallback, onResolveErrorCallback) { setTimeout(() => { onSymbolResolvedCallback({ name: symbolName, description: symbolName, type: "crypto", session: "24x7", timezone: "Etc/UTC", ticker: symbolName, minmov: 1, pricescale: 100, has_intraday: true, has_daily: true, has_weekly_and_monthly: true, supported_resolutions: ["1","3","5","15","30","60","120","240","D", ], data_status: "streaming", }); }, 0); } subscribeBars(symbolInfo, resolution, onRealtimeCallback, subscriberUID, onResetCacheNeededCallback) { if (!this.subscribers[symbolInfo.ticker]) { this.subscribers[symbolInfo.ticker] = []; } this.subscribers[symbolInfo.ticker].push({ callback: onRealtimeCallback }); this.subscribe(symbolInfo.ticker, resolution); } unsubscribeBars(subscriberUID) { for (let symbol in this.subscribers) { this.subscribers[symbol] = this.subscribers[symbol].filter( (sub) => sub.uid !== subscriberUID ); } }}