diff --git a/dashboard/inject.mjs b/dashboard/inject.mjs index 962da19..1b067a7 100644 --- a/dashboard/inject.mjs +++ b/dashboard/inject.mjs @@ -571,6 +571,19 @@ export async function synthesize(data) { 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 const yfWti = yfQuotes['CL=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 }, 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 ideas: [], ideasSource: 'disabled', // newsFeed for ticker (merged RSS + GDELT + Telegram) diff --git a/dashboard/public/jarvis.html b/dashboard/public/jarvis.html index 0be7865..1d94c98 100644 --- a/dashboard/public/jarvis.html +++ b/dashboard/public/jarvis.html @@ -1344,6 +1344,7 @@ const payrolls=D.bls.find(b=>b.id==='CES0000000001'); const gscpi=D.gscpi; const mkt=D.markets||{}; + const metals=D.metals||{}; const wtiH = D.energy.wtiRecent||[]; const wtiMax=Math.max(...wtiH),wtiMin=Math.min(...wtiH); @@ -1365,11 +1366,15 @@ const vixFred = D.fred.find(f=>f.id==='VIXCLS'); const vixVal = vixLive?.value || vixFred?.value; 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=[ {l:'WTI Crude',v:`$${D.energy.wti}`,s:'$/bbl',p:70}, {l:'Brent',v:`$${D.energy.brent}`,s:'$/bbl',p:75}, {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:'Fed Funds',v:ff?`${ff.value}%`:'--',s:ff?.date||'',p:36}, {l:'GSCPI',v:gscpi?gscpi.value.toFixed(2):'--',s:gscpi?.interpretation||'',p:49}, @@ -1383,6 +1388,8 @@ return f?.recent?.length > 1 ? {spark: f.recent, sparkUp: up} : {}; }; 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 const indexCards = (mkt.indexes||[]).map(mktCard).join(''); @@ -1456,7 +1463,7 @@
${cryptoCards}
`:''}
-
ENERGY + MACRO
+
ENERGY + METALS + MACRO
${metrics.map(m=>{ const sparkSvg = m.spark ? mkSparkSvg(m.spark, m.sparkUp) : ''; return `
${m.l}
${m.v}${sparkSvg}${m.s}
`; diff --git a/lib/alerts/discord.mjs b/lib/alerts/discord.mjs index 24b9969..ba0ce3f 100644 --- a/lib/alerts/discord.mjs +++ b/lib/alerts/discord.mjs @@ -428,7 +428,7 @@ export class DiscordAlerter { const highs = signals.filter(s => s.severity === 'high'); const nukeSignal = signals.find(s => s.key === 'nuke_anomaly'); 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)); if (nukeSignal) { diff --git a/lib/alerts/telegram.mjs b/lib/alerts/telegram.mjs index 580a902..f7bba56 100644 --- a/lib/alerts/telegram.mjs +++ b/lib/alerts/telegram.mjs @@ -222,7 +222,7 @@ export class TelegramAlerter { const highs = signals.filter(s => s.severity === 'high'); const nukeSignal = signals.find(s => s.key === 'nuke_anomaly'); 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)); // FLASH: nuclear anomaly, or ≥3 critical signals across domains @@ -667,7 +667,7 @@ Respond with ONLY valid JSON: const sections = []; // 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 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)); diff --git a/lib/delta/engine.mjs b/lib/delta/engine.mjs index ec45ddb..34e0c85 100644 --- a/lib/delta/engine.mjs +++ b/lib/delta/engine.mjs @@ -13,6 +13,8 @@ const DEFAULT_NUMERIC_THRESHOLDS = { wti: 3, brent: 3, natgas: 5, + gold: 2, + silver: 3, unemployment: 2, fed_funds: 1, '10y_yield': 3, @@ -41,6 +43,8 @@ const NUMERIC_METRICS = [ { key: 'wti', extract: d => d.energy?.wti, label: 'WTI Crude' }, { key: 'brent', extract: d => d.energy?.brent, label: 'Brent Crude' }, { 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: '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' }, diff --git a/lib/llm/ideas.mjs b/lib/llm/ideas.mjs index 1e78e23..f3d6020 100644 --- a/lib/llm/ideas.mjs +++ b/lib/llm/ideas.mjs @@ -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`); } + // 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 if (data.bls?.length) { sections.push(`LABOR: ${data.bls.map(b => `${b.id}=${b.value}`).join(', ')}`); diff --git a/server.mjs b/server.mjs index 157fbc8..3c094b4 100644 --- a/server.mjs +++ b/server.mjs @@ -87,6 +87,7 @@ if (telegramAlerter.isConfigured) { const tg = currentData.tg || {}; const energy = currentData.energy || {}; + const metals = currentData.metals || {}; const delta = memory.getLastDelta(); const ideas = (currentData.ideas || []).slice(0, 3); @@ -106,9 +107,10 @@ if (telegramAlerter.isConfigured) { // Key metrics const vix = currentData.fred?.find(f => f.id === 'VIXCLS'); 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 || '--'}`); - 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(''); } @@ -182,6 +184,7 @@ if (discordAlerter.isConfigured) { const tg = currentData.tg || {}; const energy = currentData.energy || {}; + const metals = currentData.metals || {}; const delta = memory.getLastDelta(); const ideas = (currentData.ideas || []).slice(0, 3); @@ -194,9 +197,10 @@ if (discordAlerter.isConfigured) { const vix = currentData.fred?.find(f => f.id === 'VIXCLS'); 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 || '--'}`); - 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(''); }