From ff178755c4d85ea013b418c52c2406c58dbd7471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= <78488229+viktor138irk@users.noreply.github.com> Date: Fri, 8 May 2026 03:55:55 +0900 Subject: [PATCH] Replace raw dashboard with product-style interface --- app/static/index.html | 266 ++++++++++++++++++++---------------------- 1 file changed, 126 insertions(+), 140 deletions(-) diff --git a/app/static/index.html b/app/static/index.html index 1021bc3..96cb687 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -6,70 +6,85 @@ Trade Autopilot
- Trade Autopilot - - - - - - подключение... - source: — +
+
Trade Autopilot
+
AI-бот, демо/лайв режим, live-график и автоподбор пары
+
+
+ + + + загрузка... + source: — +
+
-
+
+
+
Live цена
+
Демо баланс
+
Режим
+
+
+
+
@@ -78,13 +93,34 @@ const chartEl = document.getElementById('chart'); const chart = LightweightCharts.createChart(chartEl, { width: chartEl.clientWidth, height: chartEl.clientHeight, - layout: { background: { color: '#0f1730' }, textColor: '#d7defa' }, + layout: { background: { color: '#101936' }, textColor: '#d7defa' }, grid: { vertLines: { color: '#1b2748' }, horzLines: { color: '#1b2748' } }, timeScale: { timeVisible: true, secondsVisible: false }, }); const candleSeries = chart.addCandlestickSeries(); let socket; let currentCandle = null; +let appState = null; + +function getMarket() { return document.getElementById('marketSelect').value || 'BTCUSDT'; } +function setJson(id, obj) { document.getElementById(id).textContent = JSON.stringify(obj, null, 2); } + +async function bootstrap() { + const res = await fetch('/api/v1/app/bootstrap'); + appState = await res.json(); + const select = document.getElementById('marketSelect'); + select.innerHTML = ''; + for (const market of appState.markets || ['BTCUSDT']) { + const opt = document.createElement('option'); + opt.value = market; + opt.textContent = market; + if (market === appState.default_market) opt.selected = true; + select.appendChild(opt); + } + renderAccount(appState.account); + renderSettings(appState.bot); + await loadSelectedMarket(); +} function normalizeKline(raw) { const rows = raw.data || []; @@ -95,7 +131,6 @@ function normalizeKline(raw) { low: Number(row.low || row[4]), close: Number(row.close || row[2]), })).filter(x => Number.isFinite(x.time) && Number.isFinite(x.close)); - candles.sort((a, b) => a.time - b.time); currentCandle = candles.length ? candles[candles.length - 1] : null; return candles; @@ -104,18 +139,13 @@ function normalizeKline(raw) { function updateLiveCandle(price, tsMs) { if (!Number.isFinite(price)) return; const bucket = Math.floor((tsMs || Date.now()) / 60000) * 60; - if (!currentCandle || currentCandle.time !== bucket) { - currentCandle = { time: bucket, open: price, high: price, low: price, close: price }; - } else { - currentCandle.high = Math.max(currentCandle.high, price); - currentCandle.low = Math.min(currentCandle.low, price); - currentCandle.close = price; - } + if (!currentCandle || currentCandle.time !== bucket) currentCandle = { time: bucket, open: price, high: price, low: price, close: price }; + else { currentCandle.high = Math.max(currentCandle.high, price); currentCandle.low = Math.min(currentCandle.low, price); currentCandle.close = price; } candleSeries.update(currentCandle); document.getElementById('livePrice').textContent = price.toFixed(8).replace(/0+$/, '').replace(/\.$/, ''); } -async function loadMarket() { +async function loadSelectedMarket() { const market = getMarket(); document.getElementById('status').textContent = 'загрузка истории...'; const res = await fetch(`/api/v1/market/kline?market=${market}&period=1min&limit=200`); @@ -124,68 +154,46 @@ async function loadMarket() { chart.timeScale().fitContent(); await loadMarkers(market); connectWs(market); - loadAccount(); - loadBotSettings(); + await loadAccount(); } -function getMarket() { - return document.getElementById('market').value.trim().toUpperCase() || 'BTCUSDT'; +async function autoPickMarket() { + document.getElementById('status').textContent = 'AI выбирает пару...'; + const res = await fetch('/api/v1/bot/best-market'); + const json = await res.json(); + if (json.market) { + document.getElementById('marketSelect').value = json.market; + setJson('aiResult', json); + await loadSelectedMarket(); + } else setJson('aiResult', json); } -async function loadAccount() { - const res = await fetch('/api/v1/demo/account'); - const acc = await res.json(); +function renderAccount(acc) { + if (!acc) return; document.getElementById('balance').textContent = `${Number(acc.balance).toFixed(2)} ${acc.quote_asset}`; - document.getElementById('equity').textContent = `${Number(acc.equity).toFixed(2)} ${acc.quote_asset}`; } - -async function loadBotSettings() { - const res = await fetch('/api/v1/bot/settings'); - const json = await res.json(); - document.getElementById('botSettings').textContent = JSON.stringify(json, null, 2); +function renderSettings(settings) { + if (!settings) return; + document.getElementById('modeLabel').textContent = (settings.trade_mode || 'demo').toUpperCase(); + setJson('botSettings', settings); } - -async function analyze() { - const res = await fetch(`/api/v1/bot/analyze?market=${getMarket()}`); - const json = await res.json(); - document.getElementById('aiResult').textContent = JSON.stringify(json, null, 2); -} - -async function decide() { - const res = await fetch(`/api/v1/bot/decide?market=${getMarket()}`, { method: 'POST' }); - const json = await res.json(); - document.getElementById('aiResult').textContent = JSON.stringify(json, null, 2); -} - +async function loadAccount() { const res = await fetch('/api/v1/demo/account'); renderAccount(await res.json()); } +async function loadBotSettings() { const res = await fetch('/api/v1/bot/settings'); renderSettings(await res.json()); } +async function analyze() { const res = await fetch(`/api/v1/bot/analyze?market=${getMarket()}`); setJson('aiResult', await res.json()); } +async function decide() { const res = await fetch(`/api/v1/bot/decide?market=${getMarket()}`, { method: 'POST' }); setJson('aiResult', await res.json()); } async function autoTrade() { const res = await fetch(`/api/v1/bot/auto-trade?market=${getMarket()}`, { method: 'POST' }); - const json = await res.json(); - document.getElementById('aiResult').textContent = JSON.stringify(json, null, 2); + setJson('aiResult', await res.json()); await loadAccount(); await loadMarkers(getMarket()); } - -async function setBotEnabled(enabled) { - await fetch(`/api/v1/bot/settings?enabled=${enabled}`, { method: 'POST' }); - await loadBotSettings(); -} - -async function setMode(mode) { - const res = await fetch(`/api/v1/bot/settings?trade_mode=${mode}`, { method: 'POST' }); - const json = await res.json(); - document.getElementById('aiResult').textContent = JSON.stringify(json, null, 2); - await loadBotSettings(); -} +async function setBotEnabled(enabled) { await fetch(`/api/v1/bot/settings?enabled=${enabled}`, { method: 'POST' }); await loadBotSettings(); } +async function setMode(mode) { await fetch(`/api/v1/bot/settings?trade_mode=${mode}`, { method: 'POST' }); await loadBotSettings(); } async function loadMarkers(market) { const res = await fetch(`/api/v1/dashboard/markers?market=${market}&limit=100`); const json = await res.json(); - const markers = (json.items || []).map(item => ({ - time: item.time, - position: item.side === 'buy' ? 'belowBar' : 'aboveBar', - shape: item.side === 'buy' ? 'arrowUp' : 'arrowDown', - text: item.text, - })); + const markers = (json.items || []).map(item => ({ time: item.time, position: item.side === 'buy' ? 'belowBar' : 'aboveBar', shape: item.side === 'buy' ? 'arrowUp' : 'arrowDown', text: item.text })); candleSeries.setMarkers(markers); } @@ -193,44 +201,22 @@ function connectWs(market) { if (socket) socket.close(); const proto = location.protocol === 'https:' ? 'wss' : 'ws'; socket = new WebSocket(`${proto}://${location.host}/ws/market/${market}`); - - socket.onopen = () => { - document.getElementById('status').textContent = `live: ${market}`; - }; - + socket.onopen = () => { document.getElementById('status').textContent = `live: ${market}`; }; socket.onmessage = (event) => { const msg = JSON.parse(event.data); document.getElementById('ticker').textContent = JSON.stringify(msg, null, 2).slice(0, 1600); document.getElementById('source').textContent = `source: ${msg.source || msg.type}`; - - if (msg.type === 'live_price') { - updateLiveCandle(Number(msg.price), Number(msg.ts)); - } - if (msg.type === 'stream_warning') { - document.getElementById('status').textContent = 'fallback HTTP'; - } - - const acc = msg.demo_account; - if (acc) { - document.getElementById('balance').textContent = `${Number(acc.balance).toFixed(2)} ${acc.quote_asset}`; - document.getElementById('equity').textContent = `${Number(acc.equity).toFixed(2)} ${acc.quote_asset}`; - } - if (msg.bot_state) { - document.getElementById('botSettings').textContent = JSON.stringify(msg.bot_state, null, 2); - } - }; - - socket.onclose = () => { - document.getElementById('status').textContent = 'соединение закрыто'; - }; - - socket.onerror = () => { - document.getElementById('status').textContent = 'ошибка ws'; + if (msg.type === 'live_price') updateLiveCandle(Number(msg.price), Number(msg.ts)); + if (msg.type === 'stream_warning') document.getElementById('status').textContent = 'fallback HTTP'; + if (msg.demo_account) renderAccount(msg.demo_account); + if (msg.bot_state) renderSettings(msg.bot_state); }; + socket.onclose = () => { document.getElementById('status').textContent = 'соединение закрыто'; }; + socket.onerror = () => { document.getElementById('status').textContent = 'ошибка ws'; }; } window.addEventListener('resize', () => chart.applyOptions({ width: chartEl.clientWidth, height: chartEl.clientHeight })); -loadMarket(); +bootstrap();