Files
market_screener/ui/src/lib/MarketContext.svelte
T
2026-06-06 22:55:43 -04:00

192 lines
5.4 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
let { ctx, collapsible = false } = $props();
let expanded = $state(!collapsible); // collapsed by default when collapsible=true
const cards = $derived.by(() => {
const b = ctx?.benchmarks ?? {};
return [
{
label: '10Y Yield',
value: ctx?.riskFreeRate != null ? ctx.riskFreeRate.toFixed(2) + '%' : '—',
tip: 'US 10-year Treasury yield — the risk-free rate benchmark. Higher = tighter conditions for stocks and bonds.',
},
{
label: 'VIX',
value: ctx?.vixLevel?.toFixed(1) ?? '—',
tip: 'CBOE Volatility Index — measures expected market volatility. Above 20 = elevated fear; above 30 = high stress.',
},
{
label: 'S&P 500',
value: ctx?.sp500Price?.toLocaleString() ?? '—',
tip: 'Live S&P 500 index price — broad US large-cap benchmark.',
},
{
label: 'S&P P/E',
value: b.marketPE != null ? b.marketPE.toFixed(1) + 'x' : '—',
tip: 'Trailing P/E ratio of SPY. Used to set the INFLATED mode P/E gate (S&P P/E × 1.5 in normal rates).',
},
{
label: 'Tech P/E',
value: b.techPE != null ? b.techPE.toFixed(1) + 'x' : '—',
tip: 'Trailing P/E of XLK (tech sector ETF). Sets the tech-sector gate in INFLATED mode (XLK P/E × 1.3).',
},
{
label: 'REIT Yield',
value: b.reitYield != null ? b.reitYield.toFixed(2) + '%' : '—',
tip: 'Dividend yield of XLRE (real estate ETF). Used as the REIT minimum yield gate in INFLATED mode.',
},
{
label: 'IG Spread',
value: b.igSpread != null ? b.igSpread.toFixed(2) + '%' : '—',
tip: 'Investment-grade bond spread (LQD yield 10Y yield). Sets the bond minimum spread gate in INFLATED mode.',
},
{
label: 'Rate Regime',
value: ctx?.rateRegime ?? '—',
tip: 'HIGH (>4.5%) compresses P/E gates and tightens bond/REIT requirements. NORMAL uses looser INFLATED gates.',
},
{
label: 'Volatility',
value: ctx?.volatilityRegime ?? '—',
tip: 'Derived from VIX level — LOW (<15), NORMAL (1525), HIGH (>25). Informational; not currently gating scores.',
},
];
});
</script>
<div class="ctx-wrap">
{#if collapsible}
<button class="ctx-toggle" onclick={() => expanded = !expanded}>
<span class="ctx-toggle-label">Market Context</span>
<span class="ctx-toggle-chevron">{expanded ? '▲' : '▼'}</span>
</button>
{/if}
{#if expanded}
<div class="grid">
{#each cards as c}
<div class="card">
<div class="label-row">
<span class="label">{c.label}</span>
<span class="tip-wrap">
<span class="tip-anchor">?</span>
<span class="tip-box">{c.tip}</span>
</span>
</div>
<div class="value">{c.value}</div>
</div>
{/each}
</div>
{/if}
</div>
<style>
.ctx-wrap { margin-bottom: 20px; }
/* ── Collapsible toggle ─────────────────────────────────────────── */
.ctx-toggle {
display: flex;
align-items: center;
gap: 8px;
background: none;
border: 1px solid #1e293b;
border-radius: 6px;
padding: 6px 12px;
cursor: pointer;
margin-bottom: 10px;
}
.ctx-toggle-label {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #475569;
}
.ctx-toggle-chevron {
font-size: 9px;
color: #334155;
}
/* ── Cards grid ────────────────────────────────────────────────── */
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 10px;
margin-bottom: 8px;
}
.card { background: #1e293b; border-radius: 8px; padding: 12px 14px; }
.label-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
}
.label {
font-size: 10px;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* ── Tooltip ──────────────────────────────────────────────────── */
.tip-wrap {
position: relative;
display: inline-flex;
flex-shrink: 0;
}
.tip-anchor {
display: inline-flex;
align-items: center;
justify-content: center;
width: 13px;
height: 13px;
border-radius: 50%;
background: #1e293b;
border: 1px solid #334155;
color: #475569;
font-size: 9px;
font-weight: 700;
cursor: help;
}
.tip-box {
display: none;
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
width: 220px;
background: #1e293b;
border: 1px solid #334155;
border-radius: 6px;
padding: 8px 10px;
font-size: 11px;
color: #94a3b8;
line-height: 1.5;
z-index: 50;
pointer-events: none;
white-space: normal;
}
.tip-box::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
border: 5px solid transparent;
border-top-color: #334155;
}
.tip-wrap:hover .tip-box { display: block; }
.value { font-size: 17px; font-weight: 700; color: #f1f5f9; margin-top: 4px; }
</style>