viewof startDate = Inputs.date({label: "Start date", min: dateBounds.min, max: dateBounds.max, value: periodDefaults.start, required: true})Market Movers
QUIETSWING / FOCUS UNIVERSE
Market movers
See which stocks led, lagged, and attracted unusual volume over the period you choose.
Analysis period
Defaults to the latest session. Matching dates show that day’s volume and its previous-close price move.
latestPeriod = fetch("/api/movers").then(response => response.json())
dateBounds = latestPeriod.bounds
function loadStoredPeriod(bounds) {
try {
const stored = JSON.parse(localStorage.getItem("quietdesk-analysis-period"))
const start = stored?.start >= bounds.min && stored.start <= bounds.max ? stored.start : bounds.max
const end = stored?.end >= bounds.min && stored.end <= bounds.max ? stored.end : bounds.max
return {start, end}
} catch {
return {start: bounds.max, end: bounds.max}
}
}
periodDefaults = loadStoredPeriod(dateBounds)function dateKey(value) {
if (value == null) return null
if (typeof value === "string") return value.slice(0, 10)
if (typeof value === "number") return new Date(value).toISOString().slice(0, 10)
return value.toISOString().slice(0, 10)
}
analysisPeriodState = (() => {
const period = {start: dateKey(startDate), end: dateKey(endDate)}
try {
localStorage.setItem("quietdesk-analysis-period", JSON.stringify(period))
} catch {
// Browser storage is optional; the page still works without it.
}
return period
})()
function formatPercent(value) {
return `${value >= 0 ? "+" : ""}${value.toFixed(2)}%`
}
function renderPriceChange(value) {
if (value == null) return html`<span class="muted">Unavailable</span>`
const kind = value >= 0 ? "gainer" : "loser"
return html`<span class="change ${kind}">${formatPercent(value)}</span>`
}
function formatMarketCap(value) {
if (!value) return "Unknown"
return value === "mid" ? "Mid" : value.charAt(0).toUpperCase() + value.slice(1)
}
function formatPricePosition(value) {
if (value == null) return html`<span class="muted">Unavailable</span>`
const bucket = value >= 67 ? "high" : value <= 33 ? "low" : "mid"
const label = bucket === "high" ? "High" : bucket === "low" ? "Low" : "Mid"
return html`<span class="position-badge ${bucket}">${value.toFixed(0)}%</span><span class="position-label">${label}</span>`
}
function renderTable(rows, kind, title, subtitle) {
const body = rows.map((row, index) => html`
<tr>
<td class="rank">${String(index + 1).padStart(2, "0")}</td>
<td class="ticker">${row.ticker}</td>
<td class="change ${kind}">${formatPercent(row.change)}</td>
<td class="position">${formatPricePosition(row.pricePosition)}</td>
<td class="market-cap">${formatMarketCap(row.marketCap)}</td>
<td class="themes">${row.themes.length ? row.themes.map(theme => html`<span class="theme-tag">${theme}</span>`) : html`<span class="muted">Unassigned</span>`}</td>
</tr>
`)
return html`
<section class="mover-panel ${kind}">
<div class="panel-heading">
<div>
<div class="panel-kicker">${kind === "gainer" ? "UP" : "DOWN"}</div>
<h2>${title}</h2>
</div>
<div class="panel-subtitle">${subtitle}</div>
</div>
<table class="mover-table">
<thead>
<tr><th>#</th><th>Ticker</th><th>Change</th><th>3M position</th><th>Cap</th><th>Themes</th></tr>
</thead>
<tbody>${body}</tbody>
</table>
</section>
`
}
function renderVolumeTable(rows) {
const body = rows.map((row, index) => html`
<tr>
<td class="rank">${String(index + 1).padStart(2, "0")}</td>
<td class="ticker">${row.ticker}</td>
<td>${renderPriceChange(row.priceChange)}</td>
<td class="volume-score">${row.periodMeanZ.toFixed(2)}</td>
<td class="volume-peak">${row.periodPeakZ.toFixed(2)}</td>
<td class="spike-days">${row.spikeDays} / ${row.validDays}</td>
<td class="spike-rate">${row.spikeRate.toFixed(0)}%</td>
<td class="market-cap">${formatMarketCap(row.marketCap)}</td>
<td class="themes">${row.themes.length ? row.themes.map(theme => html`<span class="theme-tag">${theme}</span>`) : html`<span class="muted">Unassigned</span>`}</td>
</tr>
`)
return html`
<section class="mover-panel volume-panel">
<div class="panel-heading">
<div>
<div class="panel-kicker">VOLUME</div>
<h2>Volume attention</h2>
</div>
<div class="panel-subtitle">Ranked by how unusual trading volume was over the period</div>
</div>
<div class="table-scroll">
<table class="mover-table volume-table">
<thead>
<tr><th>#</th><th>Ticker</th><th>Price change</th><th>Period score</th><th>Peak z</th><th>Spike days</th><th>Spike rate</th><th>Cap</th><th>Themes</th></tr>
</thead>
<tbody>${body}</tbody>
</table>
</div>
</section>
`
}periodData = dateKey(startDate) === latestPeriod.bounds.max && dateKey(endDate) === latestPeriod.bounds.max
? latestPeriod
: fetch(`/api/movers?start=${dateKey(startDate)}&end=${dateKey(endDate)}`).then(response => response.json())
periodMovers = periodData.movers
periodVolume = periodData.volume(() => {
const result = periodMovers
if (result.error) return html`<div class="empty-state">${result.error}</div>`
const period = `${result.actualStart} → ${result.actualEnd}`
return html`
<div class="period-line">Trading dates used: <strong>${period}</strong></div>
<div class="movers-grid">
${renderTable(result.gainers, "gainer", "Top gainers", "Highest period returns")}
${renderTable(result.losers, "loser", "Top losers", "Lowest period returns")}
</div>
`
})()(() => {
const result = periodVolume
if (result.error) return html`<div class="empty-state volume-empty">${result.error}</div>`
const period = result.actualStart === result.actualEnd
? result.actualEnd
: `${result.actualStart} → ${result.actualEnd}`
return html`
<div class="volume-section">
<div class="period-line">Volume dates used: <strong>${period}</strong> · ${result.tradingDays} session${result.tradingDays === 1 ? "" : "s"}</div>
${renderVolumeTable(result.stocks.slice(0, 20))}
</div>
`
})()
Universe: about 2,500 US-listed stocks with active options. 3M position shows where the latest close sits in its three-month range (0% = low, 100% = high). Volume scores compare each day’s trading volume with the stock’s own recent norm; spike days are sessions with exceptionally heavy volume. Market-cap labels come from a periodic universe snapshot.