Spaces:
Running
Running
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- index.html +516 -19
index.html
CHANGED
|
@@ -1,19 +1,516 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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>
|