/
quantvector
/
Frontend-web
Обзор
Документация
Войти
/
quantvector
/
Frontend-web
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
master
dashboard.html
641 строка
21 KB
YRM
Initial commit
04 май 2026, 21:47
04 май 2026, 21:47
85bd8bf
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Дашборд бэктестов</title> <script src="https://unpkg.com/lightweight-charts@4.1.0/dist/lightweight-charts.standalone.production.js"></script> <style> * { font-family: Arial, sans-serif; box-sizing: border-box; } body { margin: 0; background: #0b0b0f; color: #fff; display: flex; height: 100vh; overflow: hidden; } /* Анимированный фон */ body::before { content: ""; position: fixed; width: 200%; height: 200%; background: repeating-linear-gradient(60deg, rgba(168, 85, 247, 0.05), rgba(168, 85, 247, 0.05) 2px, transparent 2px, transparent 40px); animation: bg 25s linear infinite; z-index: -1; } @keyframes bg { to { transform: translate(-300px, -300px); } } /* Боковое меню */ .sidebar { width: 160px; background: #0f0f14; padding: 20px; border-right: 1px solid #1a1a22; } .sidebar a { display: block; margin-bottom: 15px; color: #888; text-decoration: none; transition: color 0.3s; } .sidebar a:hover { color: #a855f7; } /* Основной блок */ .main { flex: 1; display: flex; flex-direction: column; } /* Верхняя панель */ .topbar { display: flex; justify-content: space-between; align-items: center; padding: 15px 25px; border-bottom: 1px solid #1a1a22; } .title { font-size: 26px; font-weight: bold; } /* Аккаунт с ПЛАВНОЙ АНИМАЦИЕЙ */ .account { position: relative; padding: 8px 15px; background: linear-gradient(45deg, #7c3aed, #a855f7); border-radius: 20px; cursor: pointer; user-select: none; } .dropdown { position: absolute; right: 0; top: 55px; /* Чуть ниже для эффекта выплывания */ width: 200px; background: #0f0f14; border: 1px solid #a855f7; box-shadow: 0 0 20px rgba(168, 85, 247, 0.3); border-radius: 12px; padding: 15px; z-index: 100; /* Анимация */ opacity: 0; visibility: hidden; transform: translateY(-10px); transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; } .dropdown.active { opacity: 1; visibility: visible; transform: translateY(0); } .user-info { text-align: center; margin-bottom: 15px; } .user-email { font-size: 12px; color: #888; margin-top: 5px; } /* Кнопка выхода */ .logout { padding: 10px; text-align: center; background: #ff2d2d; border-radius: 8px; cursor: pointer; font-weight: bold; animation: pulse 1.5s infinite; } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(255, 45, 45, 0.5); } 70% { box-shadow: 0 0 0 10px rgba(255, 45, 45, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 45, 45, 0); } } /* Контент */ .content { flex: 1; display: flex; overflow: hidden; } /* Обертка для графика и статистики */ .chart-wrap { flex: 1; display: flex; flex-direction: column; padding: 20px; position: relative; } /* Панель статистики */ .stats-panel { display: flex; gap: 20px; margin-bottom: 15px; background: #111; padding: 15px 25px; border-radius: 12px; border: 1px solid #1a1a22; } .stat-box { display: flex; flex-direction: column; } .stat-label { font-size: 12px; color: #888; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 1px; } .stat-value { font-size: 20px; font-weight: bold; } .profit-positive { color: #26a69a; } .profit-negative { color: #ef5350; } /* Кнопка переключения типа графика - ТЕПЕРЬ КЛИКАБЕЛЬНАЯ */ .chart-controls { position: absolute; top: 110px; left: 40px; z-index: 50; /* Поверх графика */ } .toggle-btn { background: #1a1a22; border: 1px solid #a855f7; color: white; padding: 8px 15px; border-radius: 8px; cursor: pointer; font-size: 12px; transition: background 0.3s, box-shadow 0.3s; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); } .toggle-btn:hover { background: #2a0a3d; box-shadow: 0 0 10px rgba(168, 85, 247, 0.5); } #chart { flex: 1; width: 100%; background: #111; border-radius: 12px; overflow: hidden; position: relative; z-index: 1; /* График под кнопкой */ } /* Правая панель настроек */ .settings { width: 320px; background: #0f0f14; padding: 20px; border-left: 1px solid #1a1a22; display: flex; flex-direction: column; overflow-y: auto; } .settings-group { margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #1a1a22; } label { display: block; font-size: 13px; color: #aaa; margin-bottom: 8px; } input[type=number], select { width: 100%; padding: 10px; margin-bottom: 15px; border-radius: 8px; border: 1px solid #a855f7; background: #1a1a22; color: white; outline: none; font-size: 14px; } .slider-block { margin-bottom: 15px; } .slider-label { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 8px; color: #ddd; } input[type=range] { width: 100%; height: 6px; background: linear-gradient(90deg, #2a0a3d, #3b0f5c); border-radius: 5px; appearance: none; } input[type=range]::-webkit-slider-thumb { appearance: none; width: 16px; height: 16px; background: #a855f7; border-radius: 50%; box-shadow: 0 0 10px #a855f7; cursor: pointer; } .run-btn { margin-top: auto; width: 100%; padding: 15px; background: linear-gradient(45deg, #7c3aed, #a855f7); border: none; border-radius: 12px; color: white; font-weight: bold; font-size: 14px; cursor: pointer; transition: opacity 0.3s; } .run-btn:hover { opacity: 0.9; } .run-btn:disabled { background: #555; cursor: not-allowed; color: #aaa; } </style> </head> <body> <div class="sidebar"> <a href="#">Дашборд</a> <a href="history.html">История</a> <a href="#" onclick="logout(event)">Выйти</a> </div> <div class="main"> <div class="topbar"> <div class="title">Дашборд</div> <div class="account" onclick="toggleAccount(event)"> Аккаунт ▾ <div class="dropdown" id="dropdown" onclick="event.stopPropagation()"> <div class="user-info"> <b id="userName">Пользователь</b> <div class="user-email" id="userEmail">user@mail.ru</div> </div> <div class="logout" onclick="logout(event)">ВЫЙТИ</div> </div> </div> </div> <div class="content"> <div class="chart-wrap"> <!-- Статистика --> <div class="stats-panel"> <div class="stat-box"> <div class="stat-label">Итоговый баланс</div> <div class="stat-value" id="valFinalBalance">$0.00</div> </div> <div class="stat-box"> <div class="stat-label">Профит</div> <div class="stat-value" id="valProfit">$0.00 (0.00%)</div> </div> </div> <!-- Управление графиком --> <div class="chart-controls"> <button class="toggle-btn" id="toggleChartBtn" onclick="toggleChartMode()">Режим: Линия</button> </div> <div id="chart"></div> </div> <div class="settings"> <div class="settings-group"> <label>Торговая пара</label> <select id="symbol"> <option value="BTCUSDT">Bitcoin (BTC/USDT)</option> <option value="ETHUSDT">Ethereum (ETH/USDT)</option> <option value="SOLUSDT">Solana (SOL/USDT)</option> <option value="BNBUSDT">Binance Coin (BNB/USDT)</option> </select> <label>Таймфрейм</label> <select id="timeframe"> <option value="1h">1 Час</option> <option value="1d">1 День</option> <option value="1w">1 Неделя</option> <option value="1m">1 Месяц</option> </select><label>Начальный баланс ($)</label> <input type="number" id="start_balance" placeholder="Начальный баланс" value="1000"> </div> <div class="settings-group"> <label>Стратегия</label> <select id="strategySelect"> <option value="RSI_Oscillator">RSI (Индекс относительной силы)</option> <option value="SMA_Cross">SMA (Скользящая средняя)</option> <option value="MACD">MACD</option> <option value="Bollinger_Bands">Линии Боллинджера</option> </select> <div id="params"></div> </div> <button class="run-btn" id="runBtn" onclick="run()">НАЧАТЬ БЭКТЕСТ</button> </div> </div> </div> <script> document.addEventListener("DOMContentLoaded", () => { // Получаем данные из localStorage const savedEmail = localStorage.getItem("userEmail"); const savedLogin = localStorage.getItem("userLogin"); // Если данные есть, подставляем их в меню аккаунта if (savedEmail) { document.getElementById("userEmail").innerText = savedEmail; } if (savedLogin) { document.getElementById("userName").innerText = savedLogin; } }); /* ========================================= */ /* Аккаунт и Выход (Ваш старый код ниже) */ function toggleAccount(event) { document.getElementById("dropdown").classList.toggle("active"); } /* Аккаунт и Выход */ function toggleAccount(event) { document.getElementById("dropdown").classList.toggle("active"); } document.addEventListener("click", function (event) { const accountBtn = document.querySelector('.account'); if (!accountBtn.contains(event.target)) { document.getElementById("dropdown").classList.remove("active"); } }); function logout(event) { if (event) event.preventDefault(); localStorage.removeItem("token"); localStorage.removeItem("userEmail") localStorage.removeItem("userLogin") window.location.href = "index.html"; } /* Инициализация графика */ const chartElement = document.getElementById("chart"); const chart = LightweightCharts.createChart(chartElement, { layout: { background: { color: "transparent" }, textColor: "#fff" }, grid: { vertLines: { color: '#222' }, horzLines: { color: '#222' } } }); // Создаем два типа серии: Линию и Свечи const lineSeries = chart.addLineSeries({ color: "#a855f7", lineWidth: 2 }); const candleSeries = chart.addCandlestickSeries({ upColor: '#26a69a', downColor: '#ef5350', borderVisible: false, wickUpColor: '#26a69a', wickDownColor: '#ef5350' }); // По умолчанию свечи скрыты let isCandleMode = false; candleSeries.applyOptions({ visible: false }); /* Переключение типа графика */ function toggleChartMode() { isCandleMode = !isCandleMode; const btn = document.getElementById("toggleChartBtn"); if (isCandleMode) { lineSeries.applyOptions({ visible: false }); candleSeries.applyOptions({ visible: true }); btn.innerText = "Режим: Свечи"; } else { lineSeries.applyOptions({ visible: true }); candleSeries.applyOptions({ visible: false }); btn.innerText = "Режим: Линия"; } } window.addEventListener('resize', () => { chart.resize(chartElement.clientWidth, chartElement.clientHeight); }); /* Шаблон слайдеров */ function slider(id, label, min, max, val, step = 1) { return ` <div class="slider-block"> <div class="slider-label"> <span>${label}</span> <span id="${id}_v" style="color: #a855f7; font-weight: bold;">${val}</span> </div> <input type="range" id="${id}" min="${min}" max="${max}" value="${val}" step="${step}"> </div>`; } /* Отрисовка параметров стратегии */ const paramsDiv = document.getElementById("params"); const strategySelect = document.getElementById("strategySelect"); function renderParams() { let s = strategySelect.value; let h = ""; if (s === "RSI_Oscillator") { h += slider("period", "Период", 5, 50, 14); h += slider("buy_level", "Уровень покупки", 10, 40, 30); h += slider("sell_level", "Уровень продажи", 60, 90, 70); } else if (s === "SMA_Cross") { h += slider("fast_period", "Быстрый период", 5, 50, 10); h += slider("slow_period", "Медленный период", 50, 200, 50); } else if (s === "MACD") { h += slider("fast_period", "Быстрая EMA", 5, 50, 12); h += slider("slow_period", "Медленная EMA", 20, 100, 26); h += slider("signal_period", "Сигнальная линия", 5, 30, 9); } else if (s === "Bollinger_Bands") { h += slider("window", "Размер окна", 10, 100, 20); h += slider("deviation", "Отклонение", 1, 3, 2, 0.1); } paramsDiv.innerHTML = h; paramsDiv.querySelectorAll("input").forEach(i => { let v = document.getElementById(i.id + "_v"); i.addEventListener("input", () => v.innerText = i.value); }); } strategySelect.onchange = renderParams; renderParams(); // Вызов при загрузке страницы /* Запуск бэктеста */ async function run() { const btn = document.getElementById("runBtn"); btn.innerText = "ЗАГРУЗКА..."; btn.disabled = true; let params = {}; paramsDiv.querySelectorAll("input").forEach(i => { params[i.id] = Number(i.value); }); const startBalance = Number(document.getElementById("start_balance").value); const token = localStorage.getItem("token") || ""; const payload = { platform: "web", token: token, need_chart: true, settings: { symbol: document.getElementById("symbol").value, timeframe: document.getElementById("timeframe").value, start_balance: startBalance, fee_percent: 0.1 }, strategy: { name: strategySelect.value, params: params } }; const SERVER_URL = "http://100.96.16.111:8080/api/backtest/run"; try { const res = await fetch(SERVER_URL, { method: "POST", headers: { "Content-Type": "application/json", ...(token ? { "Authorization": token } : {}) }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error(`Ошибка сервера! Статус: ${res.status}`); const data = await res.json(); if (data.chart_data && data.chart_data.length > 0) { let prevBal = startBalance; const lineData = []; const candleData = []; data.chart_data.forEach(x => { lineData.push({ time: x.time, value: x.balance }); const open = x.open !== undefined ? x.open : prevBal; const close = x.close !== undefined ? x.close : x.balance; const high = x.high !== undefined ? x.high : Math.max(open, close); const low = x.low !== undefined ? x.low : Math.min(open, close); candleData.push({ time: x.time, open, high, low, close }); prevBal = close; }); lineSeries.setData(lineData); candleSeries.setData(candleData); chart.timeScale().fitContent(); // Статистика const finalBalance = lineData[lineData.length - 1].value; const netProfit = finalBalance - startBalance; const profitPct = (netProfit / startBalance) * 100; document.getElementById("valFinalBalance").innerText = `$${finalBalance.toFixed(2)}`; const profitEl = document.getElementById("valProfit"); profitEl.innerText = `${netProfit >= 0 ? '+' : ''}$${netProfit.toFixed(2)} (${profitPct.toFixed(2)}%)`; profitEl.className = `stat-value ${netProfit >= 0 ? 'profit-positive' : 'profit-negative'}`; } else { alert("Сервер вернул пустые данные графика."); } } catch (error) { console.error(error); alert("Ошибка: " + error.message); } finally { btn.innerText = "НАЧАТЬ БЭКТЕСТ"; btn.disabled = false; } } </script> </body> </html>