Barry8 commited on
Commit
ab87831
·
verified ·
1 Parent(s): af3eb4a

Develop a web application that utilizes a WebSocket connection to retrieve all currently active and available Binance Futures trading pairs. This list should be presented to the user, allowing them to select all pairs, use a search function to filter the list, or simply scroll and click to choose the specific pairs they wish to monitor. Based on the user's selected timeframe (options being 1-minute, 5-minute, 15-minute, 1-hour, 4-hour, 1-day, or 1-week), the application will then display a compact, 'mini-chart' for each selected token. All these charts must be arranged neatly and compactly on a single screen, optimized for a standard PC screen ratio to ensure all elements fit. Each individual mini-chart should be zoomable and draggable using the mouse. The application should accommodate as many charts as the user selects, up to a maximum that maintains optimal screen ratio and chart size, all displayed simultaneously and updating live in one view. This setup provides a comprehensive overview of all selected tokens at a glance, facilitating the easy identification of pairs warranting further investigation. A grid layout would be highly effective for this arrangement. A suggested starting size for each chart is approximately 500 pixels wide by 250 pixels high. When the mouse hovers over a chart area, a simple price-only tooltip should appear (no other text), featuring a semi-transparent background. Additionally, a crosshair should be included to indicate the mouse position and the corresponding prices at that point. A volume indicator must be included at the bottom of each chart. All charting elements must be clearly visible, with the candles and volume bars rendered at 100% opacity (fully non-transparent). The name of each token should be displayed in clear text in the top-left corner, positioned outside the chart box. The following data points should be optionally displayable next to the token name: the live current last price of the token, live 24-hour volume, live Open Interest (OI), the funding rate, and a countdown timer until the next funding event. These optional display settings should be managed via a sliding sidebar that contains all non-essential settings and information, while the main page retains only the charts and the timeframe dropdown selector. By default, these optional data points should not be displayed. If selected to be displayed, they must be positioned beside the token name. All optional data streams should update continuously and live until the application is closed. Furthermore, include a night mode on/off toggle. The application must remember the user's settings, including the last-used tokens and all other display preferences.

Browse files
Files changed (1) hide show
  1. index.html +516 -19
index.html CHANGED
@@ -1,19 +1,516 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Binance Futures Dashboard</title>
7
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css">
8
+ <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
9
+ <script src="https://cdn.jsdelivr.net/npm/lightweight-charts@3.8.0/dist/lightweight-charts.standalone.production.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
13
+ <script src="https://cdn.tailwindcss.com"></script>
14
+ <style>
15
+ .chart-container {
16
+ width: 500px;
17
+ height: 250px;
18
+ margin: 10px;
19
+ position: relative;
20
+ }
21
+ .chart-grid {
22
+ display: grid;
23
+ grid-template-columns: repeat(auto-fill, minmax(500px, 1fr));
24
+ gap: 10px;
25
+ padding: 20px;
26
+ }
27
+ #sidebar {
28
+ width: 0;
29
+ position: fixed;
30
+ right: 0;
31
+ top: 0;
32
+ height: 100%;
33
+ overflow-x: hidden;
34
+ transition: 0.5s;
35
+ background-color: #f8f9fa;
36
+ z-index: 1000;
37
+ }
38
+ #main {
39
+ transition: margin-left .5s;
40
+ }
41
+ .tooltip {
42
+ position: absolute;
43
+ background-color: rgba(0, 0, 0, 0.7);
44
+ color: white;
45
+ padding: 5px;
46
+ border-radius: 3px;
47
+ pointer-events: none;
48
+ font-size: 12px;
49
+ }
50
+ .dark-mode {
51
+ background-color: #1a202c;
52
+ color: white;
53
+ }
54
+ .dark-mode .chart-container {
55
+ background-color: #2d3748;
56
+ }
57
+ </style>
58
+ </head>
59
+ <body class="bg-gray-100">
60
+ <div id="main">
61
+ <header class="bg-white shadow-sm p-4 flex justify-between items-center">
62
+ <h1 class="text-xl font-bold">Binance Futures Dashboard</h1>
63
+ <div class="flex space-x-4">
64
+ <select id="timeframe" class="p-2 border rounded">
65
+ <option value="1m">1m</option>
66
+ <option value="5m">5m</option>
67
+ <option value="15m">15m</option>
68
+ <option value="1h">1h</option>
69
+ <option value="4h">4h</option>
70
+ <option value="1d">1d</option>
71
+ <option value="1w">1w</option>
72
+ </select>
73
+ <button id="settingsBtn" class="p-2 bg-blue-500 text-white rounded">⚙️ Settings</button>
74
+ <button id="darkModeToggle" class="p-2 bg-gray-200 rounded">🌙</button>
75
+ </div>
76
+ </header>
77
+
78
+ <div class="p-4">
79
+ <select id="pairSelector" class="w-full p-2 border rounded" multiple="multiple"></select>
80
+ <button id="addPairsBtn" class="mt-2 p-2 bg-green-500 text-white rounded">Add Selected Pairs</button>
81
+ </div>
82
+
83
+ <div id="chartGrid" class="chart-grid"></div>
84
+ </div>
85
+
86
+ <div id="sidebar" class="p-4">
87
+ <button id="closeSidebar" class="float-right">✕</button>
88
+ <h2 class="text-lg font-bold mb-4">Settings</h2>
89
+ <div class="mb-4">
90
+ <h3 class="font-bold mb-2">Display Options</h3>
91
+ <label class="flex items-center space-x-2">
92
+ <input type="checkbox" id="showPrice" class="form-checkbox">
93
+ <span>Show Price</span>
94
+ </label>
95
+ <label class="flex items-center space-x-2">
96
+ <input type="checkbox" id="showVolume" class="form-checkbox">
97
+ <span>Show 24h Volume</span>
98
+ </label>
99
+ <label class="flex items-center space-x-2">
100
+ <input type="checkbox" id="showOI" class="form-checkbox">
101
+ <span>Show Open Interest</span>
102
+ </label>
103
+ <label class="flex items-center space-x-2">
104
+ <input type="checkbox" id="showFunding" class="form-checkbox">
105
+ <span>Show Funding Rate</span>
106
+ </label>
107
+ <label class="flex items-center space-x-2">
108
+ <input type="checkbox" id="showFundingTimer" class="form-checkbox">
109
+ <span>Show Funding Timer</span>
110
+ </label>
111
+ </div>
112
+ <div class="mb-4">
113
+ <h3 class="font-bold mb-2">Chart Style</h3>
114
+ <label class="block mb-2">
115
+ <span class="block mb-1">Candle Up Color</span>
116
+ <div id="candleUpColor" class="w-full h-8 border"></div>
117
+ </label>
118
+ <label class="block mb-2">
119
+ <span class="block mb-1">Candle Down Color</span>
120
+ <div id="candleDownColor" class="w-full h-8 border"></div>
121
+ </label>
122
+ </div>
123
+ <button id="saveSettings" class="p-2 bg-blue-500 text-white rounded w-full">Save Settings</button>
124
+ </div>
125
+
126
+ <script>
127
+ // Global variables
128
+ let ws;
129
+ const charts = {};
130
+ const subscribedPairs = new Set();
131
+ let colorTheme = {
132
+ up: '#26a69a',
133
+ down: '#ef5350',
134
+ background: '#ffffff',
135
+ text: '#333333'
136
+ };
137
+
138
+ // DOM elements
139
+ const pairSelector = $('#pairSelector');
140
+ const chartGrid = document.getElementById('chartGrid');
141
+ const sidebar = document.getElementById('sidebar');
142
+ const settingsBtn = document.getElementById('settingsBtn');
143
+ const closeSidebar = document.getElementById('closeSidebar');
144
+ const darkModeToggle = document.getElementById('darkModeToggle');
145
+ const timeframeSelector = document.getElementById('timeframe');
146
+
147
+ // Initialize UI components
148
+ $(document).ready(function() {
149
+ // Initialize Select2 for pair selection
150
+ pairSelector.select2({
151
+ placeholder: "Search and select trading pairs",
152
+ allowClear: true,
153
+ width: '100%'
154
+ });
155
+
156
+ // Initialize color pickers
157
+ const candleUpPicker = Pickr.create({
158
+ el: '#candleUpColor',
159
+ theme: 'classic',
160
+ default: colorTheme.up,
161
+ components: {
162
+ preview: true,
163
+ opacity: true,
164
+ hue: true,
165
+ interaction: {
166
+ hex: true,
167
+ rgba: true,
168
+ input: true
169
+ }
170
+ }
171
+ });
172
+
173
+ const candleDownPicker = Pickr.create({
174
+ el: '#candleDownColor',
175
+ theme: 'classic',
176
+ default: colorTheme.down,
177
+ components: {
178
+ preview: true,
179
+ opacity: true,
180
+ hue: true,
181
+ interaction: {
182
+ hex: true,
183
+ rgba: true,
184
+ input: true
185
+ }
186
+ }
187
+ });
188
+
189
+ // Load saved settings
190
+ loadSettings();
191
+
192
+ // Initialize WebSocket connection
193
+ initWebSocket();
194
+
195
+ // Fetch available pairs
196
+ fetchPairs();
197
+
198
+ // Event listeners
199
+ document.getElementById('addPairsBtn').addEventListener('click', addSelectedPairs);
200
+ settingsBtn.addEventListener('click', openSidebar);
201
+ closeSidebar.addEventListener('click', closeSidebar);
202
+ darkModeToggle.addEventListener('click', toggleDarkMode);
203
+ document.getElementById('saveSettings').addEventListener('click', saveSettings);
204
+ timeframeSelector.addEventListener('change', changeTimeframe);
205
+ });
206
+
207
+ function initWebSocket() {
208
+ ws = new WebSocket('wss://fstream.binance.com/ws');
209
+
210
+ ws.onopen = () => {
211
+ console.log('WebSocket connected');
212
+ // Resubscribe to any previously subscribed pairs
213
+ subscribedPairs.forEach(pair => {
214
+ subscribeToPair(pair);
215
+ });
216
+ };
217
+
218
+ ws.onclose = () => {
219
+ console.log('WebSocket disconnected, attempting to reconnect...');
220
+ setTimeout(initWebSocket, 1000);
221
+ };
222
+
223
+ ws.onerror = (error) => {
224
+ console.error('WebSocket error:', error);
225
+ };
226
+
227
+ ws.onmessage = (event) => {
228
+ const message = JSON.parse(event.data);
229
+ processWebSocketMessage(message);
230
+ };
231
+ }
232
+
233
+ function fetchPairs() {
234
+ fetch('https://fapi.binance.com/fapi/v1/exchangeInfo')
235
+ .then(response => response.json())
236
+ .then(data => {
237
+ const pairs = data.symbols
238
+ .filter(symbol => symbol.status === 'TRADING')
239
+ .map(symbol => ({
240
+ id: symbol.symbol,
241
+ text: symbol.symbol
242
+ }));
243
+
244
+ pairSelector.select2({ data: pairs });
245
+ })
246
+ .catch(error => console.error('Error fetching pairs:', error));
247
+ }
248
+
249
+ function addSelectedPairs() {
250
+ const selectedPairs = pairSelector.val();
251
+ if (!selectedPairs) return;
252
+
253
+ selectedPairs.forEach(pair => {
254
+ if (!subscribedPairs.has(pair)) {
255
+ subscribedPairs.add(pair);
256
+ subscribeToPair(pair);
257
+ createChart(pair);
258
+ }
259
+ });
260
+ }
261
+
262
+ function subscribeToPair(pair) {
263
+ if (ws && ws.readyState === WebSocket.OPEN) {
264
+ const timeframe = timeframeSelector.value;
265
+ const subscription = {
266
+ method: "SUBSCRIBE",
267
+ params: [
268
+ `${pair.toLowerCase()}@kline_${timeframe}`,
269
+ `${pair.toLowerCase()}@markPrice@1s`,
270
+ `${pair.toLowerCase()}@bookTicker`
271
+ ],
272
+ id: Date.now()
273
+ };
274
+ ws.send(JSON.stringify(subscription));
275
+ }
276
+ }
277
+
278
+ function createChart(pair) {
279
+ const container = document.createElement('div');
280
+ container.className = 'chart-container';
281
+ container.id = `chart-${pair}`;
282
+
283
+ const title = document.createElement('div');
284
+ title.className = 'font-bold mb-2';
285
+ title.id = `title-${pair}`;
286
+ title.textContent = pair;
287
+
288
+ const chartElement = document.createElement('div');
289
+ chartElement.style.width = '100%';
290
+ chartElement.style.height = '100%';
291
+
292
+ container.appendChild(title);
293
+ container.appendChild(chartElement);
294
+ chartGrid.appendChild(container);
295
+
296
+ const chart = LightweightCharts.createChart(chartElement, {
297
+ width: 500,
298
+ height: 250,
299
+ layout: {
300
+ backgroundColor: colorTheme.background,
301
+ textColor: colorTheme.text
302
+ },
303
+ grid: {
304
+ vertLines: { color: '#eee' },
305
+ horzLines: { color: '#eee' }
306
+ },
307
+ crosshair: {
308
+ mode: LightweightCharts.CrosshairMode.Normal
309
+ },
310
+ timeScale: {
311
+ timeVisible: true,
312
+ secondsVisible: false
313
+ }
314
+ });
315
+
316
+ const candleSeries = chart.addCandlestickSeries({
317
+ upColor: colorTheme.up,
318
+ downColor: colorTheme.down,
319
+ borderVisible: false,
320
+ wickUpColor: colorTheme.up,
321
+ wickDownColor: colorTheme.down
322
+ });
323
+
324
+ const volumeSeries = chart.addHistogramSeries({
325
+ color: '#26a69a',
326
+ priceFormat: {
327
+ type: 'volume'
328
+ },
329
+ priceScaleId: '',
330
+ scaleMargins: {
331
+ top: 0.8,
332
+ bottom: 0
333
+ }
334
+ });
335
+
336
+ charts[pair] = {
337
+ chart,
338
+ candleSeries,
339
+ volumeSeries,
340
+ lastUpdate: null,
341
+ data: []
342
+ };
343
+
344
+ // Fetch initial historical data
345
+ fetchInitialData(pair);
346
+ }
347
+
348
+ function fetchInitialData(pair) {
349
+ const timeframe = timeframeSelector.value;
350
+ const limit = 100;
351
+
352
+ fetch(`https://fapi.binance.com/fapi/v1/klines?symbol=${pair}&interval=${timeframe}&limit=${limit}`)
353
+ .then(response => response.json())
354
+ .then(data => {
355
+ const formattedData = data.map(item => ({
356
+ time: item[0] / 1000,
357
+ open: parseFloat(item[1]),
358
+ high: parseFloat(item[2]),
359
+ low: parseFloat(item[3]),
360
+ close: parseFloat(item[4]),
361
+ volume: parseFloat(item[5])
362
+ }));
363
+
364
+ const volumes = data.map(item => ({
365
+ time: item[0] / 1000,
366
+ value: parseFloat(item[5]),
367
+ color: parseFloat(item[4]) >= parseFloat(item[1]) ? colorTheme.up : colorTheme.down
368
+ }));
369
+
370
+ if (charts[pair]) {
371
+ charts[pair].candleSeries.setData(formattedData);
372
+ charts[pair].volumeSeries.setData(volumes);
373
+ charts[pair].data = formattedData;
374
+ }
375
+ })
376
+ .catch(error => console.error(`Error fetching initial data for ${pair}:`, error));
377
+ }
378
+
379
+ function processWebSocketMessage(message) {
380
+ if (message.e === 'kline') {
381
+ const pair = message.s;
382
+ const kline = message.k;
383
+
384
+ if (charts[pair]) {
385
+ const newCandle = {
386
+ time: kline.t / 1000,
387
+ open: parseFloat(kline.o),
388
+ high: parseFloat(kline.h),
389
+ low: parseFloat(kline.l),
390
+ close: parseFloat(kline.c),
391
+ volume: parseFloat(kline.v)
392
+ };
393
+
394
+ // Update or add candle
395
+ if (kline.x) {
396
+ // Closed candle
397
+ charts[pair].data.push(newCandle);
398
+ if (charts[pair].data.length > 100) {
399
+ charts[pair].data.shift();
400
+ }
401
+ charts[pair].candleSeries.update(newCandle);
402
+ } else {
403
+ // Update current candle
404
+ charts[pair].candleSeries.update(newCandle);
405
+ }
406
+
407
+ // Update volume
408
+ const newVolume = {
409
+ time: kline.t / 1000,
410
+ value: parseFloat(kline.v),
411
+ color: parseFloat(kline.c) >= parseFloat(kline.o) ? colorTheme.up : colorTheme.down
412
+ };
413
+ charts[pair].volumeSeries.update(newVolume);
414
+ }
415
+ }
416
+ // Handle other message types (mark price, book ticker, etc.)
417
+ }
418
+
419
+ function changeTimeframe() {
420
+ const timeframe = timeframeSelector.value;
421
+ subscribedPairs.forEach(pair => {
422
+ // Unsubscribe from old streams
423
+ if (ws && ws.readyState === WebSocket.OPEN) {
424
+ const unsubscribe = {
425
+ method: "UNSUBSCRIBE",
426
+ params: [
427
+ `${pair.toLowerCase()}@kline_${timeframe}`,
428
+ `${pair.toLowerCase()}@markPrice@1s`,
429
+ `${pair.toLowerCase()}@bookTicker`
430
+ ],
431
+ id: Date.now()
432
+ };
433
+ ws.send(JSON.stringify(unsubscribe));
434
+ }
435
+
436
+ // Subscribe to new streams
437
+ subscribeToPair(pair);
438
+
439
+ // Refresh chart data
440
+ if (charts[pair]) {
441
+ fetchInitialData(pair);
442
+ }
443
+ });
444
+ }
445
+
446
+ function openSidebar() {
447
+ sidebar.style.width = '300px';
448
+ document.getElementById('main').style.marginRight = '300px';
449
+ }
450
+
451
+ function closeSidebar() {
452
+ sidebar.style.width = '0';
453
+ document.getElementById('main').style.marginRight = '0';
454
+ }
455
+
456
+ function toggleDarkMode() {
457
+ document.body.classList.toggle('dark-mode');
458
+ // Update all charts
459
+ Object.keys(charts).forEach(pair => {
460
+ charts[pair].chart.applyOptions({
461
+ layout: {
462
+ backgroundColor: document.body.classList.contains('dark-mode') ? '#2d3748' : '#ffffff',
463
+ textColor: document.body.classList.contains('dark-mode') ? '#ffffff' : '#333333'
464
+ },
465
+ grid: {
466
+ vertLines: { color: document.body.classList.contains('dark-mode') ? '#4a5568' : '#eee' },
467
+ horzLines: { color: document.body.classList.contains('dark-mode') ? '#4a5568' : '#eee' }
468
+ }
469
+ });
470
+ });
471
+ }
472
+
473
+ function saveSettings() {
474
+ const settings = {
475
+ showPrice: document.getElementById('showPrice').checked,
476
+ showVolume: document.getElementById('showVolume').checked,
477
+ showOI: document.getElementById('showOI').checked,
478
+ showFunding: document.getElementById('showFunding').checked,
479
+ showFundingTimer: document.getElementById('showFundingTimer').checked,
480
+ darkMode: document.body.classList.contains('dark-mode'),
481
+ selectedPairs: Array.from(subscribedPairs),
482
+ timeframe: timeframeSelector.value
483
+ };
484
+ localStorage.setItem('binanceDashboardSettings', JSON.stringify(settings));
485
+ closeSidebar();
486
+ }
487
+
488
+ function loadSettings() {
489
+ const savedSettings = localStorage.getItem('binanceDashboardSettings');
490
+ if (savedSettings) {
491
+ const settings = JSON.parse(savedSettings);
492
+ document.getElementById('showPrice').checked = settings.showPrice;
493
+ document.getElementById('showVolume').checked = settings.showVolume;
494
+ document.getElementById('showOI').checked = settings.showOI;
495
+ document.getElementById('showFunding').checked = settings.showFunding;
496
+ document.getElementById('showFundingTimer').checked = settings.showFundingTimer;
497
+
498
+ if (settings.darkMode) {
499
+ document.body.classList.add('dark-mode');
500
+ }
501
+
502
+ if (settings.selectedPairs && settings.selectedPairs.length > 0) {
503
+ settings.selectedPairs.forEach(pair => {
504
+ subscribedPairs.add(pair);
505
+ createChart(pair);
506
+ });
507
+ }
508
+
509
+ if (settings.timeframe) {
510
+ timeframeSelector.value = settings.timeframe;
511
+ }
512
+ }
513
+ }
514
+ </script>
515
+ </body>
516
+ </html>