From 3e4a3ed03ffbb0ae4143d073b88d710c3a8607f4 Mon Sep 17 00:00:00 2001 From: Tvishi Agarwal Date: Thu, 9 Apr 2026 20:15:17 +0000 Subject: [PATCH] test1 --- generate.py | 110 +++++++++++++++++++++++++++++++++++++++++----------- index.html | 31 ++++++++------- 2 files changed, 105 insertions(+), 36 deletions(-) diff --git a/generate.py b/generate.py index 00e9bd4..447faa7 100644 --- a/generate.py +++ b/generate.py @@ -84,14 +84,14 @@ BOND_ETFS = { # ─── COMMODITY TICKERS ─────────────────────────────────────────────────────── COMMODITIES = { - "GC=F": {"name": "Gold", "unit": "$/oz"}, - "SI=F": {"name": "Silver", "unit": "$/oz"}, - "CL=F": {"name": "WTI Crude", "unit": "$/bbl"}, - "BZ=F": {"name": "Brent Crude", "unit": "$/bbl"}, - "NG=F": {"name": "Natural Gas", "unit": "$/MMBtu"}, - "HG=F": {"name": "Copper", "unit": "$/lb"}, - "ZW=F": {"name": "Wheat", "unit": "¢/bu"}, - "ZC=F": {"name": "Corn", "unit": "¢/bu"}, + "GC=F": {"key": "gold", "name": "Gold", "unit": "$/oz"}, + "SI=F": {"key": "silver", "name": "Silver", "unit": "$/oz"}, + "CL=F": {"key": "wti", "name": "WTI Crude", "unit": "$/bbl"}, + "BZ=F": {"key": "brent", "name": "Brent Crude", "unit": "$/bbl"}, + "NG=F": {"key": "natgas", "name": "Natural Gas", "unit": "$/MMBtu"}, + "HG=F": {"key": "copper", "name": "Copper", "unit": "$/lb"}, + "ZW=F": {"key": "wheat", "name": "Wheat", "unit": "¢/bu"}, + "ZC=F": {"key": "corn", "name": "Corn", "unit": "¢/bu"}, } # ─── KEY ETFs ──────────────────────────────────────────────────────────────── @@ -278,12 +278,12 @@ def fetch_finnhub_calendar(): { "date": e.get("time", "")[:10], "time": e.get("time", "")[11:16], - "country": e.get("country", ""), - "event": e.get("event", ""), - "prev": e.get("prev"), - "estimate": e.get("estimate"), - "actual": e.get("actual"), - "impact": e.get("impact", ""), + "ctry": e.get("country", ""), + "ev": e.get("event", ""), + "prev": e.get("prev") if e.get("prev") is not None else "\u2014", + "fcast": e.get("estimate") if e.get("estimate") is not None else "\u2014", + "act": e.get("actual") if e.get("actual") is not None else "\u2014", + "imp": "red" if e.get("impact") == "high" else "orange", } for e in events[:40] ] @@ -309,12 +309,16 @@ def fetch_rss_news(): title = item.findtext("title", "") link = item.findtext("link", "") pub = item.findtext("pubDate", "") + try: + pub_ts = int(datetime.strptime(pub, "%a, %d %b %Y %H:%M:%S %Z").timestamp()) + except Exception: + pub_ts = 0 if title: articles.append({ "headline": title, "source": source, "url": link, - "datetime": pub, + "datetime": pub_ts, "category": "general", }) except Exception as e: @@ -322,6 +326,28 @@ def fetch_rss_news(): return articles +def fetch_single_ticker_history(ticker, name, period="3mo"): + """Fallback: fetch a single ticker individually if batch download fails.""" + try: + data = yf.download(ticker, period=period, progress=False, threads=False) + closes = data["Close"].dropna() + if len(closes) < 2: + return None + last = float(closes.iloc[-1]) + d1 = float(closes.iloc[-2]) if len(closes) >= 2 else None + w1 = float(closes.iloc[-6]) if len(closes) >= 6 else None + m1 = float(closes.iloc[-22]) if len(closes) >= 22 else None + return { + "price": round(last, 4), + "d1_pct": round((last / d1 - 1) * 100, 2) if d1 else None, + "w1_pct": round((last / w1 - 1) * 100, 2) if w1 else None, + "m1_pct": round((last / m1 - 1) * 100, 2) if m1 else None, + } + except Exception as e: + print(f" Single ticker history error ({ticker}): {e}") + return None + + def main(): print("=" * 60) print(f"FX RADAR Data Generation — {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}") @@ -348,7 +374,19 @@ def main(): us_yields[tenor] = val print(f" US {tenor}: {val}%") time.sleep(0.2) # Rate limit - output["yields"]["US"] = us_yields + output["yields"]["US"] = { + "y1m": us_yields.get("1M"), + "y3m": us_yields.get("3M"), + "y6m": us_yields.get("6M"), + "y1": us_yields.get("1Y"), + "y2": us_yields.get("2Y"), + "y3": us_yields.get("3Y"), + "y5": us_yields.get("5Y"), + "y7": us_yields.get("7Y"), + "y10": us_yields.get("10Y"), + "y20": us_yields.get("20Y"), + "y30": us_yields.get("30Y"), + } # 2. Global yields from FRED (limited but free) print("\n[2/8] Fetching global yields from FRED...") @@ -361,7 +399,11 @@ def main(): print(f" {country} {tenor}: {val}%") time.sleep(0.2) if country_yields: - output["yields"][country] = country_yields + output["yields"][country] = { + "y10": country_yields.get("10Y"), + "y2": country_yields.get("2Y"), + "y5": country_yields.get("5Y"), + } # 3. Yield proxies from Yahoo Finance (US treasuries) print("\n[3/8] Fetching yield data from Yahoo Finance...") @@ -370,15 +412,39 @@ def main(): }) for name, data in yf_yields.items(): print(f" {name}: {data['price']}") + # Fill missing US yields from Yahoo proxies if FRED missing + us_out = output["yields"].get("US", {}) + if us_out.get("y3m") is None and yf_yields.get("US_3M"): + us_out["y3m"] = yf_yields["US_3M"]["price"] + if us_out.get("y5") is None and yf_yields.get("US_5Y"): + us_out["y5"] = yf_yields["US_5Y"]["price"] + if us_out.get("y10") is None and yf_yields.get("US_10Y"): + us_out["y10"] = yf_yields["US_10Y"]["price"] + if us_out.get("y30") is None and yf_yields.get("US_30Y"): + us_out["y30"] = yf_yields["US_30Y"]["price"] + output["yields"]["US"] = us_out output["yields"]["US_yf"] = yf_yields # 4. Commodities print("\n[4/8] Fetching commodities...") - commod_data = fetch_yf_history(COMMODITIES) - for name, data in commod_data.items(): - info = next((v for k, v in COMMODITIES.items() if v["name"] == name), {}) - data["unit"] = info.get("unit", "") - print(f" {name}: ${data['price']} ({data.get('d1_pct', '?')}%)") + commod_data_raw = fetch_yf_history({k: v["name"] for k, v in COMMODITIES.items()}) + commod_data = {} + print(" Commodity raw keys:", list(commod_data_raw.keys())) + for ticker, info in COMMODITIES.items(): + name = info["name"] + key = info["key"] + row = commod_data_raw.get(name) + if row: + row["unit"] = info.get("unit", "") + commod_data[key] = row + print(f" {name}: ${row['price']} ({row.get('d1_pct', '?')}%)") + else: + # Fallback: try single ticker download + single = fetch_single_ticker_history(ticker, name) + if single: + single["unit"] = info.get("unit", "") + commod_data[key] = single + print(f" {name} (fallback): ${single['price']} ({single.get('d1_pct', '?')}%)") output["commodities"] = commod_data # 5. Key ETFs diff --git a/index.html b/index.html index 3a61517..0778763 100644 --- a/index.html +++ b/index.html @@ -20,6 +20,7 @@ async function fFr(d,sym){try{return await jf("https://api.frankfurter.dev/v1/"+ async function fFD(){try{const r=await fetch("/.netlify/functions/fred-yields");if(!r.ok)throw new Error("fred-yields failed");return await r.json()}catch{return null}} async function fECB(){try{const r=await fetch("/.netlify/functions/ecb-yields");if(!r.ok)throw new Error("ecb-yields failed");return await r.json()}catch(err){console.error("ECB yields fetch failed",err);return null}} async function fFh(ep,p,k){if(!k)return null;try{const q=new URLSearchParams({...p,token:k}).toString();return await jf("https://finnhub.io/api/v1/"+ep+"?"+q)}catch{return null}} +async function fData(){const r=await fetch("/data.json?_="+Date.now());if(!r.ok)throw new Error("Failed to load data.json");return await r.json()} const ago=n=>{const d=new Date();d.setDate(d.getDate()-n);return d.toISOString().slice(0,10)};const fwd=n=>{const d=new Date();d.setDate(d.getDate()+n);return d.toISOString().slice(0,10)};const jan2=()=>new Date().getFullYear()+"-01-02"; const G10=[{p:"EURUSD",q:"EUR",i:1},{p:"GBPUSD",q:"GBP",i:1},{p:"USDJPY",q:"JPY",i:0},{p:"USDCHF",q:"CHF",i:0},{p:"AUDUSD",q:"AUD",i:1},{p:"NZDUSD",q:"NZD",i:1},{p:"USDCAD",q:"CAD",i:0},{p:"USDSEK",q:"SEK",i:0},{p:"USDNOK",q:"NOK",i:0}]; @@ -344,21 +345,23 @@ function App(){ const svK=useCallback(nk=>{setKeys(nk);Object.entries(nk).forEach(([k,v])=>sk(k,v))},[]); const load=useCallback(async()=>{ sLd(true); - try{const [now,d1,w1,m1,ytd]=await Promise.all([fFr("latest",SYM),fFr(ago(1),SYM),fFr(ago(7),SYM),fFr(ago(30),SYM),fFr(jan2(),SYM)]);if(now?.rates){const bld=defs=>defs.map(d=>{const s=now.rates[d.q];if(!s)return{pair:d.p,spot:null,d1:null,w1:null,m1:null,ytd:null};const sp=d.i?1/s:s;const ch=h=>{const v=h?.rates?.[d.q];if(!v)return null;return d.i?pc(1/s,1/v):pc(s,v)};return{pair:d.p,spot:sp,d1:ch(d1),w1:ch(w1),m1:ch(m1),ytd:ch(ytd)}});sG10(bld(G10));sEM(bld(EMP));sFxL(true);const eur=now.rates.EUR,eurY=d1?.rates?.EUR;if(eur)sRisk(p=>({...p,DXY:{level:+(1/eur*104.5).toFixed(2),chg:eurY?+pc(1/eur,1/eurY):null}}))}}catch{} - try{const usYields=await fFD();if(usYields){sYld(p=>({...p,US:usYields}));sYL(true);if(usYields.y10!=null)sRisk(p=>({...p,"US 10Y":{level:usYields.y10,chg:null}}))}}catch{} - try{const euYields=await fECB();if(euYields){sYld(p=>({...p,Eurozone:euYields}))}}catch(err){console.error("Eurozone yields load failed",err)} - if(keys.finnhub){ - const [vR,gR,wR,bR]=await Promise.allSettled([fFh("quote",{symbol:"CBOE:VIX"},keys.finnhub),fFh("forex/rates",{base:"USD"},keys.finnhub),fFh("quote",{symbol:"NYMEX:CL1!"},keys.finnhub),fFh("quote",{symbol:"NYMEX:BZ1!"},keys.finnhub)]); - const v=vR.status==="fulfilled"?vR.value:null;if(v?.c)sRisk(p=>({...p,VIX:{level:v.c,chg:+(v.c-v.pc).toFixed(2)}})); - const g=gR.status==="fulfilled"?gR.value:null;if(g?.quote?.XAU){const gp=1/g.quote.XAU;sCmd(p=>({...p,gold:{level:gp,chg:null}}));sRisk(p=>({...p,Gold:{level:gp,chg:null}}));} - const w=wR.status==="fulfilled"?wR.value:null;if(w?.c){sCmd(p=>({...p,wti:{level:w.c,chg:w.pc?+pc(w.c,w.pc):null}}));sRisk(p=>({...p,WTI:{level:w.c,chg:w.pc?+pc(w.c,w.pc):null}}));} - const b=bR.status==="fulfilled"?bR.value:null;if(b?.c){sCmd(p=>({...p,brent:{level:b.c,chg:b.pc?+pc(b.c,b.pc):null}}));sRisk(p=>({...p,Brent:{level:b.c,chg:b.pc?+pc(b.c,b.pc):null}}));} - sRL(true)} - sLd(false);sLR(new Date()); - },[keys]); + try{ + const data=await fData(); + const mapFx=defs=>defs.map(d=>{const x=data.fx?.[d.p];return{pair:d.p,spot:x?.price??null,d1:x?.d1_pct??null,w1:x?.w1_pct??null,m1:x?.m1_pct??null,ytd:null}}); + sG10(mapFx(G10));sEM(mapFx(EMP));sFxL(true); + const mappedYields={}; + if(data.yields){Object.entries(data.yields).forEach(([k,v])=>{if(k!=="US_yf")mappedYields[k]=v})} + sYld(mappedYields);sYL(true); + const dxy=data.fx?.DXY;const vix=data.volatility?.["VIX (S&P 500 Vol)"];const move=data.volatility?.["MOVE (Bond Vol)"];const usY=mappedYields.US||{}; + sRisk({DXY:{level:dxy?.price??null,chg:dxy?.d1_pct??null},VIX:{level:vix?.price??null,chg:vix?.change_pct??null},Gold:{level:data.commodities?.gold?.price??null,chg:data.commodities?.gold?.d1_pct??null},WTI:{level:data.commodities?.wti?.price??null,chg:data.commodities?.wti?.d1_pct??null},Brent:{level:data.commodities?.brent?.price??null,chg:data.commodities?.brent?.d1_pct??null},"US 10Y":{level:usY.y10??null,chg:null},MOVE:{level:move?.price??null,chg:move?.change_pct??null}}); + sRL(true); + sCmd({gold:{level:data.commodities?.gold?.price??null,chg:data.commodities?.gold?.d1_pct??null},wti:{level:data.commodities?.wti?.price??null,chg:data.commodities?.wti?.d1_pct??null},brent:{level:data.commodities?.brent?.price??null,chg:data.commodities?.brent?.d1_pct??null}}); + const parsedNews=(data.news||[]).map(n=>{const dt=typeof n.datetime==="number"?n.datetime:Math.floor(Date.parse(n.datetime)/1000)||0;return{...n,datetime:dt}});sNews(parsedNews);sNL(true); + const parsedCal=(data.calendar||[]).map(e=>({date:e.date||"",time:e.time||"",ctry:e.ctry||"",ev:e.ev||"",prev:e.prev??"—",fcast:e.fcast??"—",act:e.act??"—",imp:e.imp||"orange"}));sCal(parsedCal);sCL(true); + sLR(new Date()); + }catch(err){console.error("Failed to load dashboard data",err)}finally{sLd(false)} + },[]); useEffect(()=>{load()},[load]); - useEffect(()=>{if((sec==="deep"||sec==="brief")&&keys.finnhub&&!nL){(async()=>{try{const nd=await fFh("news",{category:"forex"},keys.finnhub);if(Array.isArray(nd)){sNews(nd.slice(0,30));sNL(true)}}catch{}})()}},[sec,keys.finnhub,nL]); - useEffect(()=>{if(sec==="cal"&&keys.finnhub&&!cL){(async()=>{try{const cd=await fFh("calendar/economic",{from:ago(0),to:fwd(7)},keys.finnhub);if(cd?.economicCalendar?.length){const fl={"US":"🇺🇸","EU":"🇪🇺","GB":"🇬🇧","JP":"🇯🇵","CH":"🇨🇭","AU":"🇦🇺","NZ":"🇳🇿","CA":"🇨🇦","SE":"🇸🇪","NO":"🇳🇴","BR":"🇧🇷","MX":"🇲🇽","ZA":"🇿🇦","IN":"🇮🇳","KR":"🇰🇷","TR":"🇹🇷","PL":"🇵🇱","CN":"🇨🇳","DE":"🇩🇪"};sCal(cd.economicCalendar.slice(0,30).map(e=>({date:e.time?.slice(0,10)||"",time:e.time?.slice(11,16)||"",ctry:fl[e.country]||e.country||"",ev:e.event||"",prev:e.prev!=null?String(e.prev):"—",fcast:e.estimate!=null?String(e.estimate):"—",act:e.actual!=null?String(e.actual):"—",imp:e.impact==="high"?"red":"orange"})));sCL(true)}}catch{}})()}},[sec,keys.finnhub,cL]); return