feat(markets): surface gold and silver across dashboard and briefs

This commit is contained in:
calesthio
2026-03-28 22:53:23 -07:00
parent 3f3d050382
commit 1b80539cdc
7 changed files with 46 additions and 9 deletions

View File

@@ -571,6 +571,19 @@ export async function synthesize(data) {
timestamp: yfData.summary?.timestamp || null, timestamp: yfData.summary?.timestamp || null,
}; };
const yfGold = yfQuotes['GC=F'];
const yfSilver = yfQuotes['SI=F'];
const metals = {
gold: yfGold?.price,
goldChange: yfGold?.change,
goldChangePct: yfGold?.changePct,
goldRecent: yfGold?.history?.map(h => h.close) || [],
silver: yfSilver?.price,
silverChange: yfSilver?.change,
silverChangePct: yfSilver?.changePct,
silverRecent: yfSilver?.history?.map(h => h.close) || [],
};
// Override stale EIA prices with live Yahoo Finance data if available // Override stale EIA prices with live Yahoo Finance data if available
const yfWti = yfQuotes['CL=F']; const yfWti = yfQuotes['CL=F'];
const yfBrent = yfQuotes['BZ=F']; const yfBrent = yfQuotes['BZ=F'];
@@ -595,7 +608,7 @@ export async function synthesize(data) {
}, },
sdr: { total: sdrNet.totalReceivers || 0, online: sdrNet.online || 0, zones: sdrZones }, sdr: { total: sdrNet.totalReceivers || 0, online: sdrNet.online || 0, zones: sdrZones },
tg: { posts: tgData.totalPosts || 0, urgent: tgUrgent, topPosts: tgTop }, tg: { posts: tgData.totalPosts || 0, urgent: tgUrgent, topPosts: tgTop },
who, fred, energy, bls, treasury, gscpi, defense, noaa, epa, acled, gdelt, space, health, news, who, fred, energy, metals, bls, treasury, gscpi, defense, noaa, epa, acled, gdelt, space, health, news,
markets, // Live Yahoo Finance market data markets, // Live Yahoo Finance market data
ideas: [], ideasSource: 'disabled', ideas: [], ideasSource: 'disabled',
// newsFeed for ticker (merged RSS + GDELT + Telegram) // newsFeed for ticker (merged RSS + GDELT + Telegram)

View File

@@ -1344,6 +1344,7 @@ function renderLower(){
const payrolls=D.bls.find(b=>b.id==='CES0000000001'); const payrolls=D.bls.find(b=>b.id==='CES0000000001');
const gscpi=D.gscpi; const gscpi=D.gscpi;
const mkt=D.markets||{}; const mkt=D.markets||{};
const metals=D.metals||{};
const wtiH = D.energy.wtiRecent||[]; const wtiH = D.energy.wtiRecent||[];
const wtiMax=Math.max(...wtiH),wtiMin=Math.min(...wtiH); const wtiMax=Math.max(...wtiH),wtiMin=Math.min(...wtiH);
@@ -1365,11 +1366,15 @@ function renderLower(){
const vixFred = D.fred.find(f=>f.id==='VIXCLS'); const vixFred = D.fred.find(f=>f.id==='VIXCLS');
const vixVal = vixLive?.value || vixFred?.value; const vixVal = vixLive?.value || vixFred?.value;
const vixChg = vixLive?.changePct != null ? `${vixLive.changePct>=0?'+':''}${vixLive.changePct}%` : ''; const vixChg = vixLive?.changePct != null ? `${vixLive.changePct>=0?'+':''}${vixLive.changePct}%` : '';
const fmtMarketPrice = (price) => price != null ? `$${price.toLocaleString(undefined,{maximumFractionDigits:2})}` : '--';
const dayMove = (pct) => pct != null ? `${pct>=0?'+':''}${pct}% today` : '';
const metrics=[ const metrics=[
{l:'WTI Crude',v:`$${D.energy.wti}`,s:'$/bbl',p:70}, {l:'WTI Crude',v:`$${D.energy.wti}`,s:'$/bbl',p:70},
{l:'Brent',v:`$${D.energy.brent}`,s:'$/bbl',p:75}, {l:'Brent',v:`$${D.energy.brent}`,s:'$/bbl',p:75},
{l:'Nat Gas',v:`$${D.energy.natgas||'--'}`,s:'$/MMBtu',p:30}, {l:'Nat Gas',v:`$${D.energy.natgas||'--'}`,s:'$/MMBtu',p:30},
{l:'Gold',v:fmtMarketPrice(metals.gold),s:dayMove(metals.goldChangePct)||'COMEX proxy',p:58},
{l:'Silver',v:fmtMarketPrice(metals.silver),s:dayMove(metals.silverChangePct)||'COMEX proxy',p:54},
{l:'VIX',v:vixVal?vixVal.toFixed(1):'--',s:vixChg||'volatility index',p:vixVal?Math.min(vixVal*2.5,100):30}, {l:'VIX',v:vixVal?vixVal.toFixed(1):'--',s:vixChg||'volatility index',p:vixVal?Math.min(vixVal*2.5,100):30},
{l:'Fed Funds',v:ff?`${ff.value}%`:'--',s:ff?.date||'',p:36}, {l:'Fed Funds',v:ff?`${ff.value}%`:'--',s:ff?.date||'',p:36},
{l:'GSCPI',v:gscpi?gscpi.value.toFixed(2):'--',s:gscpi?.interpretation||'',p:49}, {l:'GSCPI',v:gscpi?gscpi.value.toFixed(2):'--',s:gscpi?.interpretation||'',p:49},
@@ -1383,6 +1388,8 @@ function renderLower(){
return f?.recent?.length > 1 ? {spark: f.recent, sparkUp: up} : {}; return f?.recent?.length > 1 ? {spark: f.recent, sparkUp: up} : {};
}; };
metrics[0] = {...metrics[0], spark: D.energy.wtiRecent, sparkUp: false}; metrics[0] = {...metrics[0], spark: D.energy.wtiRecent, sparkUp: false};
metrics[3] = {...metrics[3], spark: metals.goldRecent, sparkUp: (metals.goldChangePct ?? 0) >= 0};
metrics[4] = {...metrics[4], spark: metals.silverRecent, sparkUp: (metals.silverChangePct ?? 0) >= 0};
// Build live market cards from Yahoo Finance // Build live market cards from Yahoo Finance
const indexCards = (mkt.indexes||[]).map(mktCard).join(''); const indexCards = (mkt.indexes||[]).map(mktCard).join('');
@@ -1456,7 +1463,7 @@ function renderLower(){
<div class="metrics-row">${cryptoCards}</div> <div class="metrics-row">${cryptoCards}</div>
</div>`:''} </div>`:''}
<div style="margin-bottom:8px"> <div style="margin-bottom:8px">
<div style="font-family:var(--mono);font-size:9px;color:var(--dim);margin-bottom:4px;letter-spacing:1px">ENERGY + MACRO</div> <div style="font-family:var(--mono);font-size:9px;color:var(--dim);margin-bottom:4px;letter-spacing:1px">ENERGY + METALS + MACRO</div>
<div class="metrics-row">${metrics.map(m=>{ <div class="metrics-row">${metrics.map(m=>{
const sparkSvg = m.spark ? mkSparkSvg(m.spark, m.sparkUp) : ''; const sparkSvg = m.spark ? mkSparkSvg(m.spark, m.sparkUp) : '';
return `<div class="mc"><div class="ml">${m.l}</div><span class="mv">${m.v}${sparkSvg}</span><span class="ms">${m.s}</span><div class="mbar"><span style="width:${m.p}%"></span></div></div>`; return `<div class="mc"><div class="ml">${m.l}</div><span class="mv">${m.v}${sparkSvg}</span><span class="ms">${m.s}</span><div class="mbar"><span style="width:${m.p}%"></span></div></div>`;

View File

@@ -428,7 +428,7 @@ export class DiscordAlerter {
const highs = signals.filter(s => s.severity === 'high'); const highs = signals.filter(s => s.severity === 'high');
const nukeSignal = signals.find(s => s.key === 'nuke_anomaly'); const nukeSignal = signals.find(s => s.key === 'nuke_anomaly');
const osintNew = signals.filter(s => s.key?.startsWith('tg_urgent')); const osintNew = signals.filter(s => s.key?.startsWith('tg_urgent'));
const marketSignals = signals.filter(s => ['vix', 'hy_spread', 'wti', 'brent', '10y2y'].includes(s.key)); const marketSignals = signals.filter(s => ['vix', 'hy_spread', 'wti', 'brent', 'natgas', 'gold', 'silver', '10y2y'].includes(s.key));
const conflictSignals = signals.filter(s => ['conflict_events', 'conflict_fatalities', 'thermal_total'].includes(s.key)); const conflictSignals = signals.filter(s => ['conflict_events', 'conflict_fatalities', 'thermal_total'].includes(s.key));
if (nukeSignal) { if (nukeSignal) {

View File

@@ -222,7 +222,7 @@ export class TelegramAlerter {
const highs = signals.filter(s => s.severity === 'high'); const highs = signals.filter(s => s.severity === 'high');
const nukeSignal = signals.find(s => s.key === 'nuke_anomaly'); const nukeSignal = signals.find(s => s.key === 'nuke_anomaly');
const osintNew = signals.filter(s => s.key?.startsWith('tg_urgent')); const osintNew = signals.filter(s => s.key?.startsWith('tg_urgent'));
const marketSignals = signals.filter(s => ['vix', 'hy_spread', 'wti', 'brent', '10y2y'].includes(s.key)); const marketSignals = signals.filter(s => ['vix', 'hy_spread', 'wti', 'brent', 'natgas', 'gold', 'silver', '10y2y'].includes(s.key));
const conflictSignals = signals.filter(s => ['conflict_events', 'conflict_fatalities', 'thermal_total'].includes(s.key)); const conflictSignals = signals.filter(s => ['conflict_events', 'conflict_fatalities', 'thermal_total'].includes(s.key));
// FLASH: nuclear anomaly, or ≥3 critical signals across domains // FLASH: nuclear anomaly, or ≥3 critical signals across domains
@@ -667,7 +667,7 @@ Respond with ONLY valid JSON:
const sections = []; const sections = [];
// Categorize signals // Categorize signals
const marketSignals = signals.filter(s => ['vix', 'hy_spread', 'wti', 'brent', 'natgas', '10y2y', 'fed_funds', '10y_yield', 'usd_index'].includes(s.key)); const marketSignals = signals.filter(s => ['vix', 'hy_spread', 'wti', 'brent', 'natgas', 'gold', 'silver', '10y2y', 'fed_funds', '10y_yield', 'usd_index'].includes(s.key));
const osintSignals = signals.filter(s => s.key === 'tg_urgent' || s.item?.channel); const osintSignals = signals.filter(s => s.key === 'tg_urgent' || s.item?.channel);
const conflictSignals = signals.filter(s => ['conflict_events', 'conflict_fatalities', 'thermal_total'].includes(s.key)); const conflictSignals = signals.filter(s => ['conflict_events', 'conflict_fatalities', 'thermal_total'].includes(s.key));
const otherSignals = signals.filter(s => !marketSignals.includes(s) && !osintSignals.includes(s) && !conflictSignals.includes(s)); const otherSignals = signals.filter(s => !marketSignals.includes(s) && !osintSignals.includes(s) && !conflictSignals.includes(s));

View File

@@ -13,6 +13,8 @@ const DEFAULT_NUMERIC_THRESHOLDS = {
wti: 3, wti: 3,
brent: 3, brent: 3,
natgas: 5, natgas: 5,
gold: 2,
silver: 3,
unemployment: 2, unemployment: 2,
fed_funds: 1, fed_funds: 1,
'10y_yield': 3, '10y_yield': 3,
@@ -41,6 +43,8 @@ const NUMERIC_METRICS = [
{ key: 'wti', extract: d => d.energy?.wti, label: 'WTI Crude' }, { key: 'wti', extract: d => d.energy?.wti, label: 'WTI Crude' },
{ key: 'brent', extract: d => d.energy?.brent, label: 'Brent Crude' }, { key: 'brent', extract: d => d.energy?.brent, label: 'Brent Crude' },
{ key: 'natgas', extract: d => d.energy?.natgas, label: 'Natural Gas' }, { key: 'natgas', extract: d => d.energy?.natgas, label: 'Natural Gas' },
{ key: 'gold', extract: d => d.metals?.gold, label: 'Gold' },
{ key: 'silver', extract: d => d.metals?.silver, label: 'Silver' },
{ key: 'unemployment', extract: d => d.bls?.find(b => b.id === 'LNS14000000' || b.id === 'UNRATE')?.value, label: 'Unemployment' }, { key: 'unemployment', extract: d => d.bls?.find(b => b.id === 'LNS14000000' || b.id === 'UNRATE')?.value, label: 'Unemployment' },
{ key: 'fed_funds', extract: d => d.fred?.find(f => f.id === 'DFF')?.value, label: 'Fed Funds Rate' }, { key: 'fed_funds', extract: d => d.fred?.find(f => f.id === 'DFF')?.value, label: 'Fed Funds Rate' },
{ key: '10y_yield', extract: d => d.fred?.find(f => f.id === 'DGS10')?.value, label: '10Y Yield' }, { key: '10y_yield', extract: d => d.fred?.find(f => f.id === 'DGS10')?.value, label: '10Y Yield' },

View File

@@ -73,6 +73,15 @@ function compactSweepForLLM(data, delta, previousIdeas) {
sections.push(`ENERGY: WTI=$${data.energy.wti}, Brent=$${data.energy.brent}, NatGas=$${data.energy.natgas}, CrudeStocks=${data.energy.crudeStocks}bbl`); sections.push(`ENERGY: WTI=$${data.energy.wti}, Brent=$${data.energy.brent}, NatGas=$${data.energy.natgas}, CrudeStocks=${data.energy.crudeStocks}bbl`);
} }
// Metals
if (data.metals?.gold != null || data.metals?.silver != null) {
const gold = data.metals?.gold != null ? `$${data.metals.gold}` : 'n/a';
const silver = data.metals?.silver != null ? `$${data.metals.silver}` : 'n/a';
const goldChg = data.metals?.goldChangePct != null ? ` (${data.metals.goldChangePct >= 0 ? '+' : ''}${data.metals.goldChangePct}%)` : '';
const silverChg = data.metals?.silverChangePct != null ? ` (${data.metals.silverChangePct >= 0 ? '+' : ''}${data.metals.silverChangePct}%)` : '';
sections.push(`METALS: Gold=${gold}${goldChg}, Silver=${silver}${silverChg}`);
}
// BLS // BLS
if (data.bls?.length) { if (data.bls?.length) {
sections.push(`LABOR: ${data.bls.map(b => `${b.id}=${b.value}`).join(', ')}`); sections.push(`LABOR: ${data.bls.map(b => `${b.id}=${b.value}`).join(', ')}`);

View File

@@ -87,6 +87,7 @@ if (telegramAlerter.isConfigured) {
const tg = currentData.tg || {}; const tg = currentData.tg || {};
const energy = currentData.energy || {}; const energy = currentData.energy || {};
const metals = currentData.metals || {};
const delta = memory.getLastDelta(); const delta = memory.getLastDelta();
const ideas = (currentData.ideas || []).slice(0, 3); const ideas = (currentData.ideas || []).slice(0, 3);
@@ -106,9 +107,10 @@ if (telegramAlerter.isConfigured) {
// Key metrics // Key metrics
const vix = currentData.fred?.find(f => f.id === 'VIXCLS'); const vix = currentData.fred?.find(f => f.id === 'VIXCLS');
const hy = currentData.fred?.find(f => f.id === 'BAMLH0A0HYM2'); const hy = currentData.fred?.find(f => f.id === 'BAMLH0A0HYM2');
if (vix || energy.wti) { if (vix || energy.wti || metals.gold || metals.silver) {
sections.push(`📊 VIX: ${vix?.value || '--'} | WTI: $${energy.wti || '--'} | Brent: $${energy.brent || '--'}`); sections.push(`📊 VIX: ${vix?.value || '--'} | WTI: $${energy.wti || '--'} | Brent: $${energy.brent || '--'}`);
if (hy) sections.push(` HY Spread: ${hy.value} | NatGas: $${energy.natgas || '--'}`); sections.push(` Gold: $${metals.gold || '--'} | Silver: $${metals.silver || '--'}${hy ? ` | HY Spread: ${hy.value}` : ''}`);
sections.push(` NatGas: $${energy.natgas || '--'}`);
sections.push(''); sections.push('');
} }
@@ -182,6 +184,7 @@ if (discordAlerter.isConfigured) {
const tg = currentData.tg || {}; const tg = currentData.tg || {};
const energy = currentData.energy || {}; const energy = currentData.energy || {};
const metals = currentData.metals || {};
const delta = memory.getLastDelta(); const delta = memory.getLastDelta();
const ideas = (currentData.ideas || []).slice(0, 3); const ideas = (currentData.ideas || []).slice(0, 3);
@@ -194,9 +197,10 @@ if (discordAlerter.isConfigured) {
const vix = currentData.fred?.find(f => f.id === 'VIXCLS'); const vix = currentData.fred?.find(f => f.id === 'VIXCLS');
const hy = currentData.fred?.find(f => f.id === 'BAMLH0A0HYM2'); const hy = currentData.fred?.find(f => f.id === 'BAMLH0A0HYM2');
if (vix || energy.wti) { if (vix || energy.wti || metals.gold || metals.silver) {
sections.push(`📊 VIX: ${vix?.value || '--'} | WTI: $${energy.wti || '--'} | Brent: $${energy.brent || '--'}`); sections.push(`📊 VIX: ${vix?.value || '--'} | WTI: $${energy.wti || '--'} | Brent: $${energy.brent || '--'}`);
if (hy) sections.push(` HY Spread: ${hy.value} | NatGas: $${energy.natgas || '--'}`); sections.push(` Gold: $${metals.gold || '--'} | Silver: $${metals.silver || '--'}${hy ? ` | HY Spread: ${hy.value}` : ''}`);
sections.push(` NatGas: $${energy.natgas || '--'}`);
sections.push(''); sections.push('');
} }