ACIL FM
Dark
Refresh
Current DIR:
/home/benbot/.trash/market_ranking5/static
/
home
benbot
.trash
market_ranking5
static
Upload
Zip Selected
Delete Selected
Pilih semua
Nama
Ukuran
Permission
Aksi
main.js
14.13 MB
chmod
View
DL
Edit
Rename
Delete
Edit file: /home/benbot/.trash/market_ranking5/static/main.js
// ============ Part 3 / 3 : static/main.js ============ // Drives the 9:16 board: clock, BGM, Spot/Futures boards (bar+marquee), // Longest Spread (24h) tables, Price Comparison (24h) tables. // Uses Flask proxy endpoints: // - /api/bitget/spot_tickers // - /api/bitget/futures_tickers?productType=USDT-FUTURES // - /api/bitget/mix/history_candles?symbol=XXX&granularity=5m&limit=288 /* ========================= 0) DOM + UTILITIES ========================= */ const $ = (q, ctx = document) => ctx.querySelector(q); const $$ = (q, ctx = document) => Array.from(ctx.querySelectorAll(q)); const num = (v) => { const n = Number(v); return Number.isFinite(n) ? n : NaN; }; const fmtNum = (v, d = 6) => Number.isFinite(v) ? Number(v).toFixed(d) : ''; const fmtPct = (v) => Number.isFinite(v) ? (v >= 0 ? `+${v.toFixed(2)}` : v.toFixed(2)) : ''; const clsPct = (v) => (v > 0 ? 'pos' : v < 0 ? 'neg' : ''); const isUSDT = (s) => /USDT$/i.test(s || ''); const toLA = (ts) => new Date(ts).toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }); /* ========================= 1) CLOCK (Los Angeles, HH:mm:ss) + BGM ========================= */ (function clock() { const clk = $('#laClock'); const tzEl = $('#laTz'); function tickLA() { const now = new Date(); const t = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Los_Angeles', hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }).format(now); const tz = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Los_Angeles', timeZoneName: 'short' }).format(now).split(' ').pop(); if (clk) clk.textContent = t; if (tzEl) tzEl.textContent = `Los Angeles (${tz})`; } tickLA(); setInterval(tickLA, 1000); })(); (function bgm() { const a = $('#bgm'); if (!a) return; a.volume = .25; const tryPlay = () => a.play().catch(() => {}); tryPlay(); ['click', 'touchstart', 'keydown'].forEach(ev => window.addEventListener(ev, () => { a.muted = false; tryPlay(); }, { once: true }) ); })(); /* ========================= 2) FETCH HELPERS (≤ 0.5s loop with graceful backoff) ========================= */ const API = { spotTickers: '/api/bitget/spot_tickers', futTickers: (productType = 'USDT-FUTURES') => `/api/bitget/futures_tickers?productType=${encodeURIComponent(productType)}`, history: (symbol, granularity = '5m', limit = 288) => `/api/bitget/mix/history_candles?symbol=${encodeURIComponent(symbol)}&granularity=${encodeURIComponent(granularity)}&limit=${encodeURIComponent(limit)}` }; async function getJSON(url) { const r = await fetch(url, { cache: 'no-store' }); if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); } let baseMs = 500, backoff = 1; const schedule = (fn) => setTimeout(fn, Math.min(4000, Math.max(500, baseMs * backoff))); /* ========================= 3) BOARDS: Breadth bar + Marquee (Spot/Futures) ========================= */ function ensureRails(el) { if (el._dup) return; const a = document.createElement('div'), b = document.createElement('div'); a.className = 'rail'; b.className = 'rail'; el.append(a, b); el._a = a; el._b = b; el._dup = true; el._x = 0; } function renderMarquee(el, items) { ensureRails(el); const html = items.map(x => `<span class="chip ${x.pct > 0 ? 'up' : x.pct < 0 ? 'down' : ''}"> <strong>${x.symbol}</strong> <span>${fmtNum(x.last, x.last > 100 ? 2 : 6)}</span> <span class="pct">${fmtPct(x.pct)}%</span> </span>`).join(''); el._a.innerHTML = html; el._b.innerHTML = html; cancelAnimationFrame(el._raf); const speed = 40; // px/s const step = (t) => { const dt = (el._t ? (t - el._t) : 16) / 1000; el._t = t; el._x -= speed * dt; const w = el._a.getBoundingClientRect().width || 1; if (el._x <= -w) el._x += w; el._a.style.transform = `translateX(${el._x}px)`; el._b.style.transform = `translateX(${el._x + w}px)`; el._raf = requestAnimationFrame(step); }; el._x = 0; el._t = undefined; el._raf = requestAnimationFrame(step); } /* Breadth computation using rolling 5-minute baseline */ const spotHist = new Map(); // symbol -> [{ts, price}] const futHist = new Map(); const windowMin = 5; function trimHistory(q, keepMin) { const cutoff = Date.now() - keepMin * 60 * 1000; while (q.length && q[0].ts < cutoff) q.shift(); } function boundaryStart(min) { const tf = min * 60 * 1000; const now = Date.now(); const al = Math.floor(now / tf) * tf; return al - tf; } function baseAt(q, t0) { if (!q || !q.length) return null; for (let i = 0; i < q.length; i++) if (q[i].ts >= t0) return q[i].price; // fallback: oldest return q[0].price; } function breadthCounts(mapHist, min) { const t0 = boundaryStart(min); let up = 0, dn = 0, flat = 0, total = 0; for (const [, q] of mapHist.entries()) { if (!q || q.length < 2) continue; const base = baseAt(q, t0); const last = q[q.length - 1].price; if (!Number.isFinite(base) || !Number.isFinite(last)) continue; total++; if (last > base) up++; else if (last < base) dn++; else flat++; } return { up, dn, flat, total }; } function drawBreadth(prefix, counts) { const { up, dn, flat, total } = counts; const upPct = total ? Math.round((up / total) * 100) : 0; const midPct = total ? Math.round((flat / total) * 100) : 0; const dnPct = total ? Math.round((dn / total) * 100) : 0; $(`#${prefix}UpBar`).style.width = upPct + '%'; $(`#${prefix}MidBar`).style.width = midPct + '%'; $(`#${prefix}MidBar`).style.left = upPct + '%'; $(`#${prefix}DnBar`).style.width = dnPct + '%'; $(`#${prefix}DnBar`).style.left = (upPct + midPct) + '%'; $(`#${prefix}UpPct`).textContent = upPct; $(`#${prefix}MidPct`).textContent = midPct; $(`#${prefix}DnPct`).textContent = dnPct; $(`#${prefix}UpCnt`).textContent = up; $(`#${prefix}MidCnt`).textContent = flat; $(`#${prefix}DnCnt`).textContent = dn; $(`#${prefix}Total`).textContent = total; } /* ========================= 4) DATA LOOPS: Spot/Futures boards ========================= */ async function loopBoards() { try { const [spot, fut] = await Promise.all([ getJSON(API.spotTickers), getJSON(API.futTickers('USDT-FUTURES')) ]); // Spot const sArr = (Array.isArray(spot?.data) ? spot.data : []) .filter(t => isUSDT(String(t.symbol))) .map(t => { const sym = String(t.symbol); const last = num(t.lastPr ?? t.last ?? t.close ?? t.lastPrice); let p = num(t.change ?? t.change24h); if (Number.isFinite(p) && Math.abs(p) < 1) p *= 100; if (!Number.isFinite(p)) { const o = num(t.open24h ?? t.openPr ?? t.open ?? t.openPrice); p = (Number.isFinite(last) && Number.isFinite(o) && o) ? ((last - o) / o * 100) : NaN; } if (!spotHist.has(sym)) spotHist.set(sym, []); const q = spotHist.get(sym); q.push({ ts: Date.now(), price: last }); trimHistory(q, 20); return { symbol: sym, last, pct: p }; }); renderMarquee($('#spotRail'), sArr); drawBreadth('spot', breadthCounts(spotHist, windowMin)); // Futures const fArr = (Array.isArray(fut?.data) ? fut.data : []) .filter(t => isUSDT(String(t.symbol))) .map(t => { const sym = String(t.symbol); const last = num(t.lastPr ?? t.last ?? t.close ?? t.lastPrice); let p = num(t.change24h ?? t.change); if (Number.isFinite(p) && Math.abs(p) < 1) p *= 100; if (!Number.isFinite(p)) { const o = num(t.open24h ?? t.openPr ?? t.open ?? t.openPrice); p = (Number.isFinite(last) && Number.isFinite(o) && o) ? ((last - o) / o * 100) : NaN; } if (!futHist.has(sym)) futHist.set(sym, []); const q = futHist.get(sym); q.push({ ts: Date.now(), price: last }); trimHistory(q, 20); return { symbol: sym, last, pct: p }; }); renderMarquee($('#futRail'), fArr); drawBreadth('fut', breadthCounts(futHist, windowMin)); backoff = 1; } catch (e) { backoff = Math.min(8, backoff * 1.5); console.error('Boards loop error:', e); } finally { schedule(loopBoards); } } /* ========================= 5) RANKINGS: Spread(24h) & Price Comparison(24h) ========================= */ const spotPctMap = new Map(); // Symbol -> spot % change(24h) async function loadSpotPctMap() { try { const spot = await getJSON(API.spotTickers); const arr = Array.isArray(spot?.data) ? spot.data : []; spotPctMap.clear(); for (const t of arr) { const sym = String(t.symbol || '').toUpperCase(); if (!isUSDT(sym)) continue; const last = num(t.lastPr ?? t.last ?? t.close ?? t.lastPrice); let p = num(t.change ?? t.change24h); if (Number.isFinite(p) && Math.abs(p) < 1) p *= 100; if (!Number.isFinite(p)) { const o = num(t.open24h ?? t.openPr ?? t.open ?? t.openPrice); p = (Number.isFinite(last) && Number.isFinite(o) && o) ? ((last - o) / o * 100) : NaN; } if (Number.isFinite(p)) spotPctMap.set(sym, p); } } catch (e) { console.warn('loadSpotPctMap failed:', e.message); } } /* Candle cache for 24h high/low timestamp (5m granularity, 288 bars) */ const candleCache = new Map(); // symbol -> {asOf, high, highT, low, lowT} async function ensureCandleInfo(symbol) { const cached = candleCache.get(symbol); if (cached && Date.now() - cached.asOf < 60_000) return cached; // refresh each minute try { const j = await getJSON(API.history(symbol, '5m', 288)); const bars = Array.isArray(j?.data) ? j.data : []; // Bitget returns arrays; assume [ts, open, high, low, close, vol] order variant let hi = -Infinity, hiT = null, lo = Infinity, loT = null; for (const k of bars) { const ts = +k[0]; const h = num(k[2]); const l = num(k[3]); if (h > hi) { hi = h; hiT = ts; } if (l < lo) { lo = l; loT = ts; } } const info = { asOf: Date.now(), high: hi, highT: hiT, low: lo, lowT: loT }; candleCache.set(symbol, info); return info; } catch (e) { console.warn('ensureCandleInfo failed for', symbol, e.message); const info = { asOf: Date.now(), high: NaN, highT: null, low: NaN, lowT: null }; candleCache.set(symbol, info); return info; } } function trHTML(rank, group, d, info, futPct, spotPct) { return `<tr> <td class="left">${rank}</td> <td class="left">${group}</td> <td class="left">${d.symbol}</td> <td class="num">${fmtNum(d.last, d.last > 100 ? 2 : 6)}</td> <td class="num ${clsPct(futPct)}">${Number.isFinite(futPct) ? futPct.toFixed(2) : ''}</td> <td class="num ${clsPct(spotPct)}">${Number.isFinite(spotPct) ? spotPct.toFixed(2) : ''}</td> <td class="num">${(Number(d.volUSDT) || 0).toLocaleString('en-US')}</td> <td class="num">${fmtNum(info?.high, d.last > 100 ? 2 : 6)}</td> <td class="left">${info?.highT ? toLA(info.highT) : ''}</td> <td class="num">${fmtNum(info?.low, d.last > 100 ? 2 : 6)}</td> <td class="left">${info?.lowT ? toLA(info.lowT) : ''}</td> </tr>`; } async function loopRanks() { try { // Keep spot % map fresh (for Spot% column) await loadSpotPctMap(); const fut = await getJSON(API.futTickers('USDT-FUTURES')); const arr = (Array.isArray(fut?.data) ? fut.data : []) .filter(t => isUSDT(String(t.symbol))); // Normalize futures rows const futRows = arr.map(t => { const symbol = String(t.symbol).toUpperCase(); const last = num(t.lastPr ?? t.last ?? t.close ?? t.lastPrice); const open24 = num(t.open24h ?? t.openPr ?? t.open ?? t.openPrice); const futPct = (Number.isFinite(last) && Number.isFinite(open24) && open24) ? ((last - open24) / open24 * 100) : num(t.change24h ?? t.change); const volUSDT = num(t.usdtVolume ?? t.quoteVolume ?? t.baseVolume); const high24 = num(t.high24h ?? t.high ?? t.highestPrice); const low24 = num(t.low24h ?? t.low ?? t.lowestPrice); const spread = (Number.isFinite(high24) && Number.isFinite(low24)) ? (high24 - low24) : NaN; const dir = Number.isFinite(futPct) ? futPct : (Number.isFinite(last) && Number.isFinite(open24) ? (last - open24) : 0); return { symbol, last, futPct, volUSDT, spread, dir }; }).filter(r => Number.isFinite(r.last)); // Spread sections (Top 5 ↑ / ↓ by spread, split by direction) const spreadUp = [...futRows].filter(r => r.dir >= 0).sort((a, b) => (b.spread || 0) - (a.spread || 0)).slice(0, 5); const spreadDn = [...futRows].filter(r => r.dir < 0).sort((a, b) => (b.spread || 0) - (a.spread || 0)).slice(0, 5); // Price Comparison (24h) sections (Top 5 gainers / losers by futPct) const gainers = [...futRows].filter(r => Number.isFinite(r.futPct)).sort((a, b) => b.futPct - a.futPct).slice(0, 5); const losers = [...futRows].filter(r => Number.isFinite(r.futPct)).sort((a, b) => a.futPct - b.futPct).slice(0, 5); // Render four tables with candle info (24h high/low timestamps) async function fillTable(rows, tbodyId, groupLabel, startRank = 1) { const tbody = document.getElementById(tbodyId); if (!tbody) return; const htmlParts = []; for (let i = 0; i < rows.length; i++) { const d = rows[i]; const info = await ensureCandleInfo(d.symbol); const spotPct = spotPctMap.get(d.symbol); htmlParts.push(trHTML(startRank + i, groupLabel, d, info, d.futPct, spotPct)); } tbody.innerHTML = htmlParts.join(''); } await Promise.all([ fillTable(spreadUp, 'spreadUpTbody', 'Gainers(Spread)'), fillTable(spreadDn, 'spreadDnTbody', 'Losers(Spread)'), fillTable(gainers, 'pcUpTbody', 'Gainers(24h)'), fillTable(losers, 'pcDnTbody', 'Losers(24h)'), ]); backoff = 1; } catch (e) { backoff = Math.min(8, backoff * 1.5); console.error('Ranks loop error:', e); } finally { schedule(loopRanks); } } /* ========================= 6) START ALL LOOPS ========================= */ loopBoards(); loopRanks(); // ========== / Part 3 / 3 ==========
Simpan
Batal
Isi Zip:
Unzip
Create
Buat Folder
Buat File
Terminal / Execute
Run
Chmod Bulk
All File
All Folder
All File dan Folder
Apply