From 7907fa30f2067149c6548b25fa807dbb11fb1e34 Mon Sep 17 00:00:00 2001 From: Deep Koluguri Date: Thu, 25 Jun 2026 22:36:15 -0400 Subject: [PATCH] feat: Add 1W, 1M, 1Y returns to macro dashboard --- Dockerfile | 2 + backend/src/routes/marketAnalysis.js | 4 +- backend/src/services/fundamentalsService.js | 45 +++++++++++++++ .../dashboard/MacroIndicatorsPanel.jsx | 57 +++++++++++++------ 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index ec75128..eb1d8b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,3 +30,5 @@ EXPOSE 3010 CMD ["node", "src/server.js"] # Cache bust 20260625222919 + +# Cache bust 20260625223615 diff --git a/backend/src/routes/marketAnalysis.js b/backend/src/routes/marketAnalysis.js index 79c3c61..f11fe25 100644 --- a/backend/src/routes/marketAnalysis.js +++ b/backend/src/routes/marketAnalysis.js @@ -7,7 +7,7 @@ import express from 'express'; import NodeCache from 'node-cache'; import { UNIVERSE, getSymbolMeta, classifyCapTier } from '../services/stockUniverseService.js'; -import { fetchBasicQuote, fetchQuoteSummary, fetchTechnicals, fetchFullAnalysis } from '../services/fundamentalsService.js'; +import { fetchBasicQuote, fetchQuoteSummary, fetchTechnicals, fetchFullAnalysis, fetchMacroPerformance } from '../services/fundamentalsService.js'; import { fetchInstitutionalHolders, fetchInsiderTransactions } from '../services/institutionalHoldersService.js'; import { fetchNewsForSymbol, fetchMarketNews } from '../services/newsService.js'; import { scoreSuitability } from '../services/scoringEngine.js'; @@ -304,7 +304,7 @@ router.get('/macro', async (req, res) => { const results = await Promise.allSettled( MACRO_SYMBOLS.map(async (entry) => { - const q = await fetchBasicQuote(entry.symbol); + const q = await fetchMacroPerformance(entry.symbol); return q ? { symbol: entry.symbol, name: entry.name, ...q } : null; }) ); diff --git a/backend/src/services/fundamentalsService.js b/backend/src/services/fundamentalsService.js index b5c73b2..2d3983a 100644 --- a/backend/src/services/fundamentalsService.js +++ b/backend/src/services/fundamentalsService.js @@ -81,6 +81,51 @@ export async function fetchBasicQuote(symbol) { } } +/** + * Fetch macro performance (1D, 1W, 1M, 1Y) + */ +export async function fetchMacroPerformance(symbol) { + const cacheKey = `macro_perf_${symbol}`; + const cached = fundamentalsCache.get(cacheKey); + if (cached) return cached; + + const url = `${YF_BASE}/v8/finance/chart/${symbol}?interval=1d&range=1y`; + + try { + const data = await yfFetchAuth(url); + const result = data?.chart?.result?.[0]; + if (!result) return null; + + const closes = result.indicators?.quote?.[0]?.close?.filter(v => v != null) || []; + if (closes.length === 0) return null; + + const currentPrice = closes[closes.length - 1]; + + // Calculate returns safely based on available data length + const getRet = (daysBack) => { + if (closes.length <= daysBack) return null; + const oldPrice = closes[closes.length - 1 - daysBack]; + if (!oldPrice) return null; + return ((currentPrice - oldPrice) / oldPrice) * 100; + }; + + const q = { + symbol: symbol.toUpperCase(), + price: currentPrice, + change_pct: getRet(1), + change_1w: getRet(5), + change_1m: getRet(21), + change_1y: getRet(closes.length - 1), + }; + + fundamentalsCache.set(cacheKey, q); + return q; + } catch (err) { + console.warn(`[fundamentalsService] macro perf failed for ${symbol}:`, err.message); + return null; + } +} + /** * Fetch full fundamentals via quoteSummary v10 */ diff --git a/frontend/src/components/dashboard/MacroIndicatorsPanel.jsx b/frontend/src/components/dashboard/MacroIndicatorsPanel.jsx index d66c5e1..3b3ef62 100644 --- a/frontend/src/components/dashboard/MacroIndicatorsPanel.jsx +++ b/frontend/src/components/dashboard/MacroIndicatorsPanel.jsx @@ -47,30 +47,51 @@ export default function MacroIndicatorsPanel() { if (data.length === 0) return null; + function fmtPct(n) { + if (n == null) return '—'; + return (n >= 0 ? '+' : '') + n.toFixed(2) + '%'; + } + + function getColor(n) { + if (n == null) return 'text-slate-500'; + return n >= 0 ? 'text-emerald-400' : 'text-red-400'; + } + return (
Global Macro Context
- {data.map((item) => { - const isPositive = (item.change_pct ?? 0) >= 0; - - return ( -
-
{item.name}
-
- - {fmtPrice(item.price, item.symbol)} - - - {isPositive ? '+' : ''}{(item.change_pct ?? 0).toFixed(2)}% - + {data.map((item) => ( +
+
+
{item.name}
+ + {fmtPrice(item.price, item.symbol)} + +
+
+
+ 1D + {fmtPct(item.change_pct)} +
+
+ 1W + {fmtPct(item.change_1w)} +
+
+ 1M + {fmtPct(item.change_1m)} +
+
+ 1Y + {fmtPct(item.change_1y)}
- ); - })} +
+ ))}
);