From b659d26ad1846fb2c63ae5564792ad68346b86d7 Mon Sep 17 00:00:00 2001 From: Dione Date: Wed, 10 Jun 2026 19:17:32 +0000 Subject: [PATCH] fix: Date.UTC spread operator and EMA for rolling sats chart --- index.html | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/index.html b/index.html index dc92f78..b4e790b 100644 --- a/index.html +++ b/index.html @@ -1571,24 +1571,24 @@ function setupCumulCard(cutoff) { function setupSatsCard(cutoff) { const DAY_MS = 86400000; - /* Derive daily balances (latest snapshot per day) */ + /* Derive daily balances (latest snapshot per day) using UTC date strings */ const dailyBalances = {}; const selectedAddr = getSelectedAddresses() || []; selectedAddr.forEach(addr => { const snaps = addressSnapshots[addr] || []; snaps.forEach(snap => { - const dateStr = snap.block_timestamp.slice(0, 10); + const utcTs = new Date(snap.block_timestamp).getTime(); + const dateStr = new Date(utcTs).toISOString().slice(0, 10); const btcVal = getTokenAmount(snap?.wallet?.cbBTC, 'cbBTC') + getTokenAmount(snap?.collateral?.cbBTC, 'cbBTC'); - const ts = new Date(snap.block_timestamp).getTime(); - if (!dailyBalances[dateStr] || ts > dailyBalances[dateStr].ts) { - dailyBalances[dateStr] = { ts, dateStr, btcVal }; + if (!dailyBalances[dateStr] || utcTs > dailyBalances[dateStr].ts) { + dailyBalances[dateStr] = { ts: utcTs, dateStr, btcVal }; } else { dailyBalances[dateStr].btcVal += btcVal; } }); }); - /* Sort by date, compute daily delta (acquisition), then fill continuous timeline */ + /* Sort by date, compute daily delta (acquisition) */ const sortedDays = Object.values(dailyBalances).sort((a, b) => a.dateStr.localeCompare(b.dateStr)); const dailyDeltas = {}; sortedDays.forEach((d, i) => { @@ -1596,29 +1596,31 @@ function setupSatsCard(cutoff) { dailyDeltas[d.dateStr] = d.btcVal - prevBalance; }); - /* Fill continuous daily timeline so no days are skipped */ - const firstDate = new Date(sortedDays[0].dateStr).getTime(); - const lastDate = new Date(sortedDays[sortedDays.length - 1].dateStr).getTime(); + /* Fill continuous daily timeline — iterate every day from first to last */ + const firstUTC = Date.UTC(new Date(sortedDays[0].dateStr).getUTCFullYear(), new Date(sortedDays[0].dateStr).getUTCMonth(), new Date(sortedDays[0].dateStr).getUTCDate()); + const lastUTC = Date.UTC(new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCFullYear(), new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCMonth(), new Date(sortedDays[sortedDays.length - 1].dateStr).getUTCDate()); const continuousDays = []; - for (let t = firstDate; t <= lastDate; t += DAY_MS) { + for (let t = firstUTC; t <= lastUTC; t += DAY_MS) { const ds = new Date(t).toISOString().slice(0, 10); continuousDays.push({ ts: t, dateStr: ds, delta: dailyDeltas[ds] || 0 }); } - /* 30-day rolling average of daily deltas */ + /* Exponentially weighted moving average — older days decay so average drops when there are no acquisitions */ const filtered = continuousDays.filter(d => d.ts >= cutoff); - const rollingAvg = []; + const halfLife = 7; + const decay = Math.pow(0.5, 1 / halfLife); + let ewmaVal = filtered[0]?.delta || 0; + const emaSats = []; for (let i = 0; i < filtered.length; i++) { - const window = filtered.slice(Math.max(0, i - 29), i + 1); - const avgSats = Math.round(window.reduce((s, d) => s + d.delta, 0) / window.length * 1e8); - rollingAvg.push([filtered[i].ts, avgSats]); + ewmaVal = filtered[i].delta + decay * ewmaVal; + emaSats.push([filtered[i].ts, Math.round(ewmaVal * 1e8)]); } - const latestAvg = rollingAvg.length > 0 ? rollingAvg[rollingAvg.length - 1][1] : 0; + const latestAvg = emaSats.length > 0 ? emaSats[emaSats.length - 1][1] : 0; document.getElementById('avg-sats-val').innerText = new Intl.NumberFormat('en-US').format(Math.round(latestAvg)); const options = { chart: { id: 'instance-sats', type: 'area', height: 200, background: 'transparent', toolbar: { show: false }, sparkline: { enabled: false }, zoom: { enabled: true, type: 'x', autoScaleYaxis: true }, animations: { enabled: false } }, - series: [{ name: 'Avg Sats/Day', data: rollingAvg }], + series: [{ name: 'Avg Sats/Day', data: emaSats }], dataLabels: { enabled: false }, colors: [orangeBrandColor], stroke: { curve: 'smooth', width: 2 },