From 0529392fe70646eb2e45206566d7d29187bd92dd Mon Sep 17 00:00:00 2001 From: Tvishi Agarwal Date: Sun, 12 Apr 2026 21:05:32 +0000 Subject: [PATCH] almost done? --- index.html | 196 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 81 deletions(-) diff --git a/index.html b/index.html index cb899e2..b5c2a69 100644 --- a/index.html +++ b/index.html @@ -219,7 +219,6 @@ function Scenarios({risk,yld,g10,em,cmd}){ ; } -function CorrMx(){return
30-Day Correlation Matrix

Correlation data not available — requires a live feed
} /* ═══ CURVES ══════════════════════════════════════════════════════ */ function Curves({yld,policyRates}){const majors=["US","UK","Eurozone","Japan","Canada","Australia","New Zealand","Switzerland","Sweden","Norway"];const rows=majors.map(c=>{const y=yld[c]||{};const cb=policyRates?.[c]??null;return{c,f:RT.find(r=>r.c===c)?.f||"",cb,y2:y.y2??null,y5:y.y5??null,y10:y.y10??null,y30:y.y30??null,s2s10:(y.y2!=null&&y.y10!=null)?y.y10-y.y2:null,s10s30:(y.y10!=null&&y.y30!=null)?y.y30-y.y10:null,term:(cb!=null&&y.y10!=null)?y.y10-cb:null}});return
Curve Monitor
r.f+" "+r.c},{k:"cb",l:"Policy",rn:r=>r.cb!=null?r.cb.toFixed(2)+"%":"\u2014"},{k:"y2",l:"2Y",rn:r=>r.y2!=null?r.y2.toFixed(2)+"%":"\u2014"},{k:"y5",l:"5Y",rn:r=>r.y5!=null?r.y5.toFixed(2)+"%":"\u2014"},{k:"y10",l:"10Y",rn:r=>r.y10!=null?r.y10.toFixed(2)+"%":"\u2014"},{k:"y30",l:"30Y",rn:r=>r.y30!=null?r.y30.toFixed(2)+"%":"\u2014"},{k:"s2s10",l:"2s10s",rn:r=>r.s2s10!=null?fb(r.s2s10*100):"\u2014",cl:r=>r.s2s10!=null?cc(r.s2s10):C.txM},{k:"s10s30",l:"10s30s",rn:r=>r.s10s30!=null?fb(r.s10s30*100):"\u2014",cl:r=>r.s10s30!=null?cc(r.s10s30):C.txM},{k:"term",l:"10Y-Policy",rn:r=>r.term!=null?fb(r.term*100):"\u2014",cl:r=>r.term!=null?cc(r.term):C.txM}]} rows={rows} sm/>
} @@ -227,6 +226,99 @@ function Curves({yld,policyRates}){const majors=["US","UK","Eurozone","Japan","C /* ═══ SPREADS ═════════════════════════════════════════════════════ */ function Spreads({yld}){const us10=yld.US?.y10??null;const de10=yld.Eurozone?.y10??yld.Germany?.y10??null;const jp10=yld.Japan?.y10??null;const uk10=yld.UK?.y10??null;const it10=yld.Italy?.y10??null;const fr10=yld.France?.y10??null;const es10=yld.Spain?.y10??null;const ca10=yld.Canada?.y10??null;const au10=yld.Australia?.y10??null;const rows=[{name:"UST 10Y \u2013 Bund 10Y",spread:(us10!=null&&de10!=null)?us10-de10:null},{name:"UST 10Y \u2013 JGB 10Y",spread:(us10!=null&&jp10!=null)?us10-jp10:null},{name:"Gilt 10Y \u2013 Bund 10Y",spread:(uk10!=null&&de10!=null)?uk10-de10:null},{name:"BTP 10Y \u2013 Bund 10Y",spread:(it10!=null&&de10!=null)?it10-de10:null},{name:"OAT 10Y \u2013 Bund 10Y",spread:(fr10!=null&&de10!=null)?fr10-de10:null},{name:"Spain 10Y \u2013 Bund 10Y",spread:(es10!=null&&de10!=null)?es10-de10:null},{name:"Canada 10Y \u2013 UST 10Y",spread:(ca10!=null&&us10!=null)?ca10-us10:null},{name:"Australia 10Y \u2013 UST 10Y",spread:(au10!=null&&us10!=null)?au10-us10:null}];return
Cross-Market Spreads
r.spread!=null?fb(r.spread*100):"\u2014",cl:r=>r.spread!=null?cc(r.spread):C.txM}]} rows={rows} sm/>
} +/* ═══ DESK VIEW BUILDER ═══════════════════════════════════════════ */ +function buildDeskView({ctry,ccy,fx,y,cbRate,m,risk,headlines,yld}){ + const s2s10=(y.y2!=null&&y.y10!=null)?y.y10-y.y2:null; + const s10s30=(y.y10!=null&&y.y30!=null)?y.y30-y.y10:null; + const pGap=(cbRate!=null&&y.y10!=null)?y.y10-cbRate:null; + const frontGap=(cbRate!=null&&y.y2!=null)?y.y2-cbRate:null; + const us10=yld?.US?.y10??null; + const spreadVsUS=(us10!=null&&y.y10!=null&&ctry!=="US")?(y.y10-us10):null; + const vix=risk?.VIX,dxy=risk?.DXY,gold=risk?.Gold,wti=risk?.WTI; + + // ── Trading Narrative ── + const nL=[]; + nL.push(ctry+" ("+ccy+") is trading in a "+(fx?.d1>0.5?"constructive FX tone on the session":fx?.d1<-0.5?"softer FX tone on the session":"largely range-bound FX environment")+"."); + if(s2s10!=null){ + if(s2s10<-0.001)nL.push("The yield curve remains inverted at "+(s2s10*100).toFixed(0)+"bp (2s10s), pointing to restrictive policy and easing expectations priced further out."); + else if(s2s10<0.005)nL.push("The curve is essentially flat at "+(s2s10*100).toFixed(0)+"bp (2s10s), a transitional shape between restrictive and normal regimes."); + else nL.push("The curve is positively sloped at +"+(s2s10*100).toFixed(0)+"bp (2s10s), consistent with a more normal term structure."); + } + if(frontGap!=null){ + if(frontGap<-0.25)nL.push("Front-end yields sit below the policy rate, suggesting the market is pricing a softer path for rates."); + else if(frontGap>0.25)nL.push("Front-end yields sit above the policy rate, suggesting higher-for-longer or tightening risk remains priced."); + else nL.push("Front-end yields are broadly aligned with the current policy rate."); + } + if(spreadVsUS!=null)nL.push("The 10Y spread vs UST is "+(spreadVsUS>0?"+":"")+(spreadVsUS*100).toFixed(0)+"bp"+(spreadVsUS>0.5?", indicating a significant local risk premium.":spreadVsUS<-0.5?", reflecting lower local yields versus the US benchmark.":".")); + if(m?.stance)nL.push("Central bank stance is currently assessed as "+m.stance.toLowerCase()+"."); + // Cross-asset overlay + const vixLvl=vix?.level,dxyChg=dxy?.chg,goldChg=gold?.chg; + if(vixLvl!=null&&vixLvl>25&&goldChg>0&&dxyChg>0)nL.push("Cross-asset signals point to a defensive tone — elevated VIX, gold bid, and USD strength."); + else if(vixLvl!=null&&vixLvl<18&&dxyChg<0)nL.push("Cross-asset backdrop is supportive for carry and risk — VIX subdued, USD softer."); + + // ── Macro Sections ── + const ratesBullets=[]; + if(cbRate!=null)ratesBullets.push("Policy rate: "+cbRate.toFixed(2)+"%."); + if(y.y2!=null)ratesBullets.push("2Y yield: "+y.y2.toFixed(2)+"%."); + if(y.y5!=null)ratesBullets.push("5Y yield: "+y.y5.toFixed(2)+"%."); + if(y.y10!=null)ratesBullets.push("10Y yield: "+y.y10.toFixed(2)+"%."); + if(y.y30!=null)ratesBullets.push("30Y yield: "+y.y30.toFixed(2)+"%."); + if(s2s10!=null)ratesBullets.push("2s10s spread: "+(s2s10>0?"+":"")+(s2s10*100).toFixed(0)+"bp"+(s2s10<0?" (inverted)":s2s10<0.005?" (flat)":" (positive slope)")+"."); + if(s10s30!=null)ratesBullets.push("10s30s spread: "+(s10s30>0?"+":"")+(s10s30*100).toFixed(0)+"bp."); + if(pGap!=null)ratesBullets.push("10Y minus policy rate: "+(pGap>0?"+":"")+(pGap*100).toFixed(0)+"bp"+(pGap<0?" — long end pricing below policy.":".")); + if(spreadVsUS!=null)ratesBullets.push("10Y vs UST: "+(spreadVsUS>0?"+":"")+(spreadVsUS*100).toFixed(0)+"bp."); + + const macroBullets=[]; + if(m.cpi)macroBullets.push("CPI YoY: "+m.cpi+(m.cpiP?" (prior "+m.cpiP+")":"")+"."); + if(m.gdp)macroBullets.push("GDP: "+m.gdp+(m.gdpT?" ("+m.gdpT+")":"")+"."); + if(m.unemp)macroBullets.push("Unemployment: "+m.unemp+(m.unempT?" ("+m.unempT+")":"")+"."); + if(m.stance)macroBullets.push("CB stance: "+m.stance+"."); + if(m.last)macroBullets.push("Last decision: "+m.last+"."); + if(m.next)macroBullets.push("Next meeting: "+m.next+"."); + if(m.pricing)macroBullets.push("Market pricing: "+m.pricing+"."); + + const causeEffect=[]; + if(m.cpi){ + const cpiNum=parseFloat(m.cpi); + if(cpiNum>3)causeEffect.push("Elevated CPI → cuts repriced out → front-end yields supported → "+ccy+" backstopped by rate differentials."); + else if(cpiNum<2)causeEffect.push("Low inflation → easing expectations firm → front-end yields drift lower → "+ccy+" under pressure from rate convergence."); + } + if(s2s10!=null&&s2s10<0)causeEffect.push("Inverted curve → recession signal / late-cycle dynamics → defensive positioning favoured."); + if(wti?.chg!=null&&wti.chg>2)causeEffect.push("Oil rally → inflation expectations rise → long-end yields pushed higher → commodity FX supported."); + if(goldChg!=null&&goldChg>1&&vixLvl!=null&&vixLvl>20)causeEffect.push("Gold bid + elevated VIX → haven demand → JPY/CHF supported, risk FX under pressure."); + + // ── FX View ── + const fxDir=fx?.d1>0.5?"Constructive — "+ccy+" stronger on the day":fx?.d1<-0.5?"Defensive — "+ccy+" weaker on the day":"Neutral — "+ccy+" range-bound"; + const fxRat=fx?.d1>0.5?"Spot performance is positive, suggesting supportive macro backdrop, yield backing, or favourable positioning flows.":fx?.d1<-0.5?"Spot performance is weaker, pointing to softer domestic drivers, external headwinds, or positioning unwind.":"No strong directional break. Watch for catalyst from rates or calendar events to establish direction."; + const fxLevels=[]; + if(fx?.spot!=null)fxLevels.push("Spot: "+Number(fx.spot).toFixed(fx.spot>100?2:4)); + if(fx?.d1!=null)fxLevels.push("1D move: "+fc(fx.d1)); + if(fx?.w1!=null)fxLevels.push("1W move: "+fc(fx.w1)); + if(y.y2!=null)fxLevels.push("2Y yield: "+y.y2.toFixed(2)+"%"); + if(y.y10!=null)fxLevels.push("10Y yield: "+y.y10.toFixed(2)+"%"); + if(s2s10!=null)fxLevels.push("2s10s: "+(s2s10*100).toFixed(0)+"bp"); + + // ── Rates View ── + let rBias,rRat; + if(s2s10==null){rBias="Insufficient curve data";rRat="Not enough tenor data to assess curve regime.";} + else if(s2s10<-0.001){rBias="Inverted curve — restrictive regime";rRat="Short-end yields remain above the long end, usually consistent with restrictive policy and easing expectations priced for later. Historically associated with late-cycle dynamics.";} + else if(s2s10<0.005){rBias="Flat curve — transitional regime";rRat="The curve is essentially flat, often a transition point between restrictive and easing cycles. Duration positioning becomes more tactical.";} + else if(s2s10<0.005){rBias="Mildly positive — early normalisation";rRat="Curve is gently positive, consistent with early-stage normalisation as the market prices a return to more conventional term premium.";} + else{rBias="Positive slope — normal term structure";rRat="Long-end yields sit above the front end, consistent with normal growth/inflation pricing and positive term premium.";} + + // ── Risks ── + const risks=["Unexpected central bank rhetoric shift or inter-meeting action","Domestic inflation surprise — either direction","Global risk-off event lifting USD and safe havens"]; + if(wti?.level!=null&&wti.level>85)risks.push("Elevated energy prices feeding through to inflation expectations"); + if(vixLvl!=null&&vixLvl>25)risks.push("VIX remains elevated — volatility regime may persist"); + if(s2s10!=null&&s2s10<-0.3)risks.push("Deeply inverted curve — historical recession signal"); + if(m.next)risks.push("Upcoming CB meeting ("+m.next+") — potential for vol around decision"); + + // ── Headline Takeaways ── + const hlTakeaways=headlines.slice(0,4).map(h=>h.headline||"No headline available"); + + return{narrative:nL,macro_sections:[{title:"Rates Structure",bullets:ratesBullets.length?ratesBullets:["No rate data available."]},{title:"Macro Backdrop",bullets:macroBullets.length?macroBullets:["No macro data available."]},{title:"Cause → Effect",bullets:causeEffect.length?causeEffect:["No strong directional signals from current data."]}],fx_view:{direction:fxDir,rationale:fxRat,levels_to_watch:fxLevels.length?fxLevels:["Watch for rate/calendar catalysts"]},rates_view:{bias:rBias,rationale:rRat},key_risks:risks,headline_takeaways:hlTakeaways}; +} + /* ═══ DEEP DIVE ═══════════════════════════════════════════════════ */ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){ const ccy=cOf[ctry],fp=[...g10,...em].find(x=>x.pair===pOf[ctry]),y=yld[ctry]||{},tpl=RT.find(x=>x.c===ctry),m=macro?.[ctry]||{},cbRate=policyRates?.[ctry]??null; @@ -238,70 +330,19 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){ const policyGap=(cbRate!=null&&y.y10!=null)?y.y10-cbRate:null; const us10=yld.US?.y10??null; const localSpreadVsUS=(us10!=null&&y.y10!=null&&ctry!=="US")?y.y10-us10:null; - const [ai,setAi]=useState(null); - const [aiLd,setAiLd]=useState(false); - const [aiErr,setAiErr]=useState(""); + const [dv,setDv]=useState(null); - const gen = async () => { - setAiLd(true); - setAiErr(""); - - try { - const body = { - country: ctry, - currency: ccy, - fx: fp ? { - pair: fp.pair, - spot: fp.spot, - d1: fp.d1, - w1: fp.w1, - m1: fp.m1, - ytd: fp.ytd - } : null, - yields: y, - centralBank: tpl ? { - country: tpl.c, - policyRate: cbRate - } : null, - macro: m, - riskBar: risk, - govBonds: cBonds, - headlines: dN.slice(0, 8).map(n => ({ - headline: n.headline, - source: n.source, - datetime: n.datetime - })) - }; - - const res = await fetch("/.netlify/functions/deep-dive-summary", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(body) - }); - - const data = await res.json(); - - if (!res.ok) { - throw new Error(data.error || "Failed to generate AI summary"); - } - - setAi(data); - } catch (err) { - setAiErr(err.message || "AI request failed"); - } finally { - setAiLd(false); - } + const gen=()=>{ + const view=buildDeskView({ctry,ccy,fx:fp?{pair:fp.pair,spot:fp.spot,d1:fp.d1,w1:fp.w1,m1:fp.m1,ytd:fp.ytd}:null,y,cbRate,m,risk,headlines:dN.slice(0,8),yld}); + setDv(view); }; return
-
🧠 AI MACRO SUMMARY — {ctry} ({ccy})
+
📋 DESK NOTE — {ctry} ({ccy})
- {aiErr && ( -
- {aiErr} -
- )} - - {!ai && !aiLd && !aiErr && ( + {!dv && (
- Click "Generate View" for a live AI deep dive + Click "Generate Desk Note" for a signal-based macro view
)} - {ai && ( + {dv && ( <>
Trading Narrative
-
- {ai.narrative} +
+
{dv.narrative.map((b,i)=>
{b}
)}
@@ -344,7 +379,7 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){
- {ai.macro_sections.map((sec, i) => ( + {dv.macro_sections.map((sec, i) => (
{sec.title} @@ -365,9 +400,9 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){
FX View
-
{ai.fx_view.direction}
-
{ai.fx_view.rationale}
- {ai.fx_view.levels_to_watch.map((x, i) => ( +
{dv.fx_view.direction}
+
{dv.fx_view.rationale}
+ {dv.fx_view.levels_to_watch.map((x, i) => (
• {x}
))}
@@ -376,8 +411,8 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){
Rates View
-
{ai.rates_view.bias}
-
{ai.rates_view.rationale}
+
{dv.rates_view.bias}
+
{dv.rates_view.rationale}
@@ -386,7 +421,7 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){
Key Risks
- {ai.key_risks.map((x, i) => ( + {dv.key_risks.map((x, i) => (
• {x}
))}
@@ -395,7 +430,7 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){
Headline Takeaways
- {ai.headline_takeaways.map((x, i) => ( + {dv.headline_takeaways.map((x, i) => (
• {x}
))}
@@ -514,8 +549,8 @@ function Deep({ctry,g10,em,yld,news,nL,risk,macro,policyRates,govBonds}){ } /* ═══ BRIEF ═══════════════════════════════════════════════════════ */ -function genBr(g10,em,risk,yld,cal,cmd){const L=[];const vix=risk.VIX,dxy=risk.DXY;const vD=(vix?.chg||0)<0,dD=(dxy?.chg||0)<0;if(vD&&dD)L.push("Risk on — VIX down "+Math.abs(vix?.chg||0).toFixed(1)+" at "+(vix?.level?.toFixed(1)||"—")+", USD softer at "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). Supportive for carry.");else if(!vD&&!dD)L.push("Defensive tone — VIX up at "+(vix?.level?.toFixed(1)||"—")+", USD bid at "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). JPY/CHF finding support.");else L.push("Mixed — VIX "+(vix?.level?.toFixed(1)||"—")+" ("+fc(vix?.chg,"")+"), DXY "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). Positioning ahead of events.");const mv=[...g10,...em].filter(x=>x.d1!=null).sort((a,b)=>Math.abs(b.d1)-Math.abs(a.d1)).slice(0,4);if(mv.length)L.push("Movers: "+mv.map(x=>x.pair+" "+(x.d1>0?"▲":"▼")+Math.abs(x.d1).toFixed(2)+"%").join(", ")+".");if(cmd.gold?.level)L.push("Gold $"+cmd.gold.level.toFixed(0)+(cmd.gold.chg!=null?" ("+fc(cmd.gold.chg)+")":"")+"."+(cmd.wti?.level?" WTI $"+cmd.wti.level.toFixed(2)+(cmd.wti.chg!=null?" ("+fc(cmd.wti.chg)+")":"")+".":" "));const u2=yld.US?.y2;if(u2!=null){const tc=RT.filter(r=>r.c!=="US"&&yld[r.c]?.y2!=null).map(r=>({ccy:r.ccy,c:+((yld[r.c].y2)-u2).toFixed(2)})).filter(x=>x.c>1).sort((a,b)=>b.c-a.c).slice(0,3);L.push("US 2Y "+u2.toFixed(2)+"%."+(tc.length?" Top carry: "+tc.map(x=>x.ccy+" +"+x.c+"%").join(", ")+".":" "))}const us10=yld.US?.y10;if(u2!=null&&us10!=null){const curve=us10-u2;L.push("US curve at "+((curve*100)>0?"+":"")+(curve*100).toFixed(1)+"bp (2s10s).")}const de10=yld.Eurozone?.y10;if(us10!=null&&de10!=null){L.push("UST-Bund spread "+(((us10-de10)*100).toFixed(0))+"bp.")}const hi=cal.filter(e=>e.imp==="red").slice(0,4);if(hi.length)L.push("Watch: "+hi.map(e=>e.ctry+" "+e.ev+" ("+e.date+")").join(", ")+".");return L.join(" ")} -function Brief({g10,em,yld,risk,cal,cmd}){const [br,sBr]=useState(null);const [ts,sTs]=useState(null);const [ld,sLd]=useState(false);const gen=()=>{sLd(true);setTimeout(()=>{sBr(genBr(g10,em,risk,yld,cal,cmd));sTs(new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"}));sLd(false)},600)};return
🧠
AI Morning Brief
Live FX, yields, carry, commodities & calendar
{ts&&Generated {ts}}
{br?
{br}
:
Click "Generate Brief"
}
} +function genBr(g10,em,risk,yld,cal,cmd){const L=[];const vix=risk.VIX,dxy=risk.DXY;const vD=(vix?.chg||0)<0,dD=(dxy?.chg||0)<0;if(vD&&dD)L.push("Risk on — VIX down "+Math.abs(vix?.chg||0).toFixed(1)+" at "+(vix?.level?.toFixed(1)||"—")+", USD softer at "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). Supportive for carry.");else if(!vD&&!dD)L.push("Defensive tone — VIX up at "+(vix?.level?.toFixed(1)||"—")+", USD bid at "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). JPY/CHF finding support.");else L.push("Mixed — VIX "+(vix?.level?.toFixed(1)||"—")+" ("+fc(vix?.chg,"")+"), DXY "+(dxy?.level?.toFixed(1)||"—")+" ("+fc(dxy?.chg)+"). Positioning ahead of events.");const mv=[...g10,...em].filter(x=>x.d1!=null).sort((a,b)=>Math.abs(b.d1)-Math.abs(a.d1)).slice(0,4);if(mv.length)L.push("Movers: "+mv.map(x=>x.pair+" "+(x.d1>0?"▲":"▼")+Math.abs(x.d1).toFixed(2)+"%").join(", ")+".");if(cmd.gold?.level)L.push("Gold $"+cmd.gold.level.toFixed(0)+(cmd.gold.chg!=null?" ("+fc(cmd.gold.chg)+")":"")+"."+(cmd.wti?.level?" WTI $"+cmd.wti.level.toFixed(2)+(cmd.wti.chg!=null?" ("+fc(cmd.wti.chg)+")":"")+".":" "));const u2=yld.US?.y2;if(u2!=null){const tc=RT.filter(r=>r.c!=="US"&&yld[r.c]?.y2!=null).map(r=>({ccy:r.ccy,c:+((yld[r.c].y2)-u2).toFixed(2)})).filter(x=>x.c>1).sort((a,b)=>b.c-a.c).slice(0,3);L.push("US 2Y "+u2.toFixed(2)+"%."+(tc.length?" Top carry: "+tc.map(x=>x.ccy+" +"+x.c+"%").join(", ")+".":" "))}const us10=yld.US?.y10;if(u2!=null&&us10!=null){const curve=us10-u2;L.push("US curve at "+((curve*100)>0?"+":"")+(curve*100).toFixed(1)+"bp (2s10s).")}const de10=yld.Eurozone?.y10;if(us10!=null&&de10!=null){L.push("UST-Bund spread "+(((us10-de10)*100).toFixed(0))+"bp.")}const hi=cal.filter(e=>e.imp==="red").slice(0,4);if(hi.length)L.push("Watch: "+hi.map(e=>e.ctry+" "+e.ev+" ("+e.date+")").join(", ")+".");return L} +function Brief({g10,em,yld,risk,cal,cmd}){const [br,sBr]=useState(null);const [ts,sTs]=useState(null);const [ld,sLd]=useState(false);const gen=()=>{sLd(true);setTimeout(()=>{sBr(genBr(g10,em,risk,yld,cal,cmd));sTs(new Date().toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"}));sLd(false)},600)};return
📋
Signal-Based Morning Brief
Auto-generated from live FX, yields, carry, commodities & calendar
{ts&&Generated {ts}}
{br?
{br.map((b,i)=>
{b}
)}
:
Click "Generate Brief"
}
} function Cal({evts,live}){const [fl,sF]=useState("All");const gF=["🇺🇸","🇪🇺","🇬🇧","🇯🇵","🇨🇭","🇦🇺","🇳🇿","🇨🇦","🇸🇪","🇳🇴"];return
{["All","G10","EM","CB"].map(f=>)}
r.imp==="red"?C.r:C.am},{k:"prev",l:"Prev"},{k:"fcast",l:"Fcst"},{k:"act",l:"Act",cl:r=>r.beat==="beat"?C.g:r.beat==="miss"?C.r:r.beat==="inline"?C.am:(r.act&&r.act!=="—")?C.g:C.txM},{k:"beat",l:"B/M",cl:r=>r.beat==="beat"?C.g:r.beat==="miss"?C.r:C.am,rn:r=>r.beat==="beat"?"▲ Beat":r.beat==="miss"?"▼ Miss":r.beat==="inline"?"● Inline":"—"}]} rows={evts.filter(e=>{if(fl==="G10")return gF.includes(e.ctry);if(fl==="EM")return!gF.includes(e.ctry);if(fl==="CB")return(e.ev||"").toLowerCase().match(/rate|fomc|boj|ecb|rba|rbnz|boc/);return true})}/>
} function Sett({open,close,keys,save}){const [lk,sL]=useState(keys);useEffect(()=>sL(keys),[keys,open]);if(!open)return null;return
e.stopPropagation()}>
⚙ API Keys
{[{k:"finnhub",l:"Finnhub — VIX, commodities, news, calendar",u:"https://finnhub.io/register"}].map(a=>
{a.l}Get key →
sL({...lk,[a.k]:e.target.value})} style={{width:"100%",padding:"8px 12px",background:C.bgC,border:"1px solid "+C.bd,borderRadius:4,color:C.tx,fontSize:12,fontFamily:Mn,boxSizing:"border-box"}}/>
)}
} @@ -558,7 +593,7 @@ function App(){ return
-
FXRADAR
{[{id:"brief",l:"🧠 Brief"},{id:"sentiment",l:"🧭 Sentiment"},{id:"markets",l:"📊 Markets"},{id:"curves",l:"📈 Curves"},{id:"spreads",l:"↔ Spreads"},{id:"commod",l:"🛢 Commodities"},{id:"scenarios",l:"🎯 Scenarios"},{id:"corr",l:"🔗 Correlations"},{id:"deep",l:"🌍 Deep Dive"},{id:"cal",l:"📅 Calendar"}].map(s=>)}
+
FXRADAR
{[{id:"brief",l:"📋 Brief"},{id:"sentiment",l:"🧭 Sentiment"},{id:"markets",l:"📊 Markets"},{id:"curves",l:"📈 Curves"},{id:"spreads",l:"↔ Spreads"},{id:"commod",l:"🛢 Commodities"},{id:"scenarios",l:"🎯 Scenarios"},{id:"deep",l:"🌍 Deep Dive"},{id:"cal",l:"📅 Calendar"}].map(s=>)}
{lr&&⏱{lr.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"})}}
@@ -569,7 +604,6 @@ function App(){ {sec==="spreads"&&} {sec==="commod"&&
Commodities
} {sec==="scenarios"&&} - {sec==="corr"&&
} {sec==="deep"&&
{RT.map(c=>)}
} {sec==="cal"&&
Economic Calendar
}