-
Последнее событие
-
—
+
+
+
+
AI
+
+
+
+
AI-результат появится здесь.
+
+
Настройки
+
—
+
+
Последнее событие
+
—
@@ -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();